OpenDaylight Controller functional modules. 68/68/1
authorMadhu Venugopal <vmadhu@cisco.com>
Fri, 22 Mar 2013 22:47:35 +0000 (15:47 -0700)
committerMadhu Venugopal <vmadhu@cisco.com>
Fri, 22 Mar 2013 22:47:35 +0000 (15:47 -0700)
Change-Id: I1cd6668738099e8db3cfe83f812a92c922ced38c
Signed-off-by: Madhu Venugopal <vmadhu@cisco.com>
380 files changed:
opendaylight/arphandler/pom.xml [new file with mode: 0644]
opendaylight/arphandler/src/main/java/org/opendaylight/controller/arphandler/internal/Activator.java [new file with mode: 0644]
opendaylight/arphandler/src/main/java/org/opendaylight/controller/arphandler/internal/ArpHandler.java [new file with mode: 0644]
opendaylight/arphandler/src/test/java/org/opendaylight/controller/arphandler/internal/ArphandlerTest.java [new file with mode: 0644]
opendaylight/configuration/api/pom.xml [new file with mode: 0644]
opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationAware.java [new file with mode: 0644]
opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationAwareCommon.java [new file with mode: 0644]
opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationContainerAware.java [new file with mode: 0644]
opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationContainerService.java [new file with mode: 0644]
opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationService.java [new file with mode: 0644]
opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationServiceCommon.java [new file with mode: 0644]
opendaylight/configuration/implementation/pom.xml [new file with mode: 0644]
opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/Activator.java [new file with mode: 0644]
opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerImpl.java [new file with mode: 0644]
opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/ConfigurationImpl.java [new file with mode: 0644]
opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationAwareTest.java [new file with mode: 0644]
opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerAwareTest.java [new file with mode: 0644]
opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerImplTest.java [new file with mode: 0644]
opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationImplTest.java [new file with mode: 0644]
opendaylight/containermanager/api/pom.xml [new file with mode: 0644]
opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/IContainerAuthorization.java [new file with mode: 0644]
opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/IContainerManager.java [new file with mode: 0644]
opendaylight/containermanager/implementation/pom.xml [new file with mode: 0644]
opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/Activator.java [new file with mode: 0644]
opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/ContainerImpl.java [new file with mode: 0644]
opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/ContainerManager.java [new file with mode: 0644]
opendaylight/containermanager/implementation/src/test/java/org/opendaylight/controller/containermanager/internal/ContainerImplTest.java [new file with mode: 0644]
opendaylight/containermanager/implementation/src/test/java/org/opendaylight/controller/containermanager/internal/ContainerManagerTest.java [new file with mode: 0644]
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/sdk/pom.xml
opendaylight/forwarding/staticrouting/pom.xml [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/IForwardingStaticRouting.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/IStaticRoutingAware.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/StaticRoute.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteConfig.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/internal/Activator.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/internal/StaticRoutingImplementation.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteConfigTest.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteTest.java [new file with mode: 0644]
opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/internal/StaticRoutingImplementationTest.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/pom.xml [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowConfig.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntryInstall.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManager.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManagerAware.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroup.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupChangeListener.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupConfig.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupProvider.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/Activator.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManagerImpl.java [new file with mode: 0644]
opendaylight/forwardingrulesmanager/src/test/java/org/opendaylight/controller/forwardingrulesmanager/frmTest.java [new file with mode: 0644]
opendaylight/hosttracker/pom.xml [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/HostTracker.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/HostTrackerCallable.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java [new file with mode: 0644]
opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java [new file with mode: 0644]
opendaylight/hosttracker/src/test/java/org/opendaylight/controller/hosttracker/HostTrackerTest.java [new file with mode: 0644]
opendaylight/hosttracker/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java [new file with mode: 0644]
opendaylight/northbound/commons/pom.xml [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/AuthenticationProviderWrapper.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/RestMessages.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/WebSecurityContextRepository.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/InternalServerErrorException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/MethodNotAllowed.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/MethodNotAllowedException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/NotAcceptableException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceConflictException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceForbiddenException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceGoneException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceNotFoundException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ServiceUnavailableException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/UnauthorizedException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/UnsupportedMediaTypeException.java [new file with mode: 0644]
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/package-info.java [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/pom.xml [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowConfigs.java [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northbound/hosttracker/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/hosttracker/pom.xml [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/Hosts.java [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northbound/integrationtest/pom.xml [new file with mode: 0644]
opendaylight/northbound/integrationtest/src/test/resources/exam.properties [new file with mode: 0644]
opendaylight/northbound/integrationtest/src/test/resources/logback.xml [new file with mode: 0644]
opendaylight/northbound/staticrouting/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/staticrouting/pom.xml [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoute.java [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutes.java [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northbound/statistics/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/statistics/pom.xml [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/AllFlowStatistics.java [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/AllPortStatistics.java [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/FlowStatistics.java [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/PortStatistics.java [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/statistics/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northbound/subnets/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/subnets/pom.xml [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetConfigs.java [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthboundJAXRS.java [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/subnets/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northbound/switchmanager/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/switchmanager/pom.xml [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectorProperties.java [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeProperties.java [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northbound/topology/enunciate.xml [new file with mode: 0644]
opendaylight/northbound/topology/pom.xml [new file with mode: 0644]
opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/EdgeProperties.java [new file with mode: 0644]
opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/Topology.java [new file with mode: 0644]
opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java [new file with mode: 0644]
opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundRSApplication.java [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/northbound/topology/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/northboundtest/unit_test_suite/pom.xml [new file with mode: 0644]
opendaylight/northboundtest/unit_test_suite/src/main/java/org/opendaylight/controller/northboundtest/unittestsuite/internal/API3UnitTest.java [new file with mode: 0644]
opendaylight/northboundtest/unit_test_suite/src/main/java/org/opendaylight/controller/northboundtest/unittestsuite/internal/Activator.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/pom.xml [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IDataPacketListen.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IDataPacketMux.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IInventoryShimExternalListener.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IInventoryShimInternalListener.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IOFInventoryService.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IOFStatisticsManager.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IPluginReadServiceFilter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IRefreshInternalProvider.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/ITopologyServiceShimListener.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/IController.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/IMessageListener.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/ISwitch.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/ISwitchStateListener.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/Controller.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/ControllerIO.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/StatisticsCollector.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SwitchEvent.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SwitchHandler.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SynchronousMessage.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/Activator.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DataPacketMuxDemux.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DataPacketServices.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DescStatisticsConverter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DiscoveryService.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowConverter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerService.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowStatisticsConverter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryService.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryServiceHelper.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryServiceShim.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/OFStatisticsManager.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/PortConverter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/PortStatisticsConverter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/ReadService.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/ReadServiceFilter.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/TopologyServiceShim.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/TopologyServices.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/Utils.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6FlowMod.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6Match.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6StatsReply.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6StatsRequest.java [new file with mode: 0644]
opendaylight/protocol_plugins/openflow/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/pom.xml [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/src/main/java/org/opendaylight/controller/routing/dijkstra_implementation/internal/Activator.java [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/src/main/java/org/opendaylight/controller/routing/dijkstra_implementation/internal/DijkstraImplementation.java [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/src/main/resources/OSGI-INF/component-factory.xml [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/src/main/resources/OSGI-INF/component.xml [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/src/test/java/org/opendaylight/controller/routing/dijkstra_implementation/DijkstraTest.java [new file with mode: 0644]
opendaylight/routing/dijkstra_implementation/src/test/java/org/opendaylight/controller/routing/dijkstra_implementation/MaxThruputTest.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/Match.java
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/MatchField.java
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/match/MatchType.java
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/LLDPTLV.java
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/address/EthernetAddress.java
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/HexEncode.java
opendaylight/samples/simpleforwarding/pom.xml [new file with mode: 0644]
opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/Activator.java [new file with mode: 0644]
opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/HostNodePair.java [new file with mode: 0644]
opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/SimpleForwardingImpl.java [new file with mode: 0644]
opendaylight/samples/simpleforwarding/src/test/java/org/opendaylight/controller/samples/simpleforwarding/internal/HostSwitchTest.java [new file with mode: 0644]
opendaylight/statisticsmanager/pom.xml [new file with mode: 0644]
opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/IStatisticsManager.java [new file with mode: 0644]
opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/StatisticsManager.java [new file with mode: 0644]
opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/internal/Activator.java [new file with mode: 0644]
opendaylight/statisticsmanager/src/test/java/org/opendaylight/controller/statisticsmanager/StatisticsManagerTest.java [new file with mode: 0644]
opendaylight/switchmanager/pom.xml [new file with mode: 0755]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/IInventoryListener.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISpanAware.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISwitchManager.java [new file with mode: 0755]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISwitchManagerAware.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SpanConfig.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/Subnet.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SubnetConfig.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/Switch.java [new file with mode: 0755]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SwitchConfig.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/internal/Activator.java [new file with mode: 0644]
opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/internal/SwitchManagerImpl.java [new file with mode: 0755]
opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/SubnetTest.java [new file with mode: 0644]
opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/SwitchTest.java [new file with mode: 0644]
opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/internal/SwitchManagerImplTest.java [new file with mode: 0644]
opendaylight/topologymanager/pom.xml [new file with mode: 0755]
opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/ITopologyManager.java [new file with mode: 0644]
opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/ITopologyManagerAware.java [new file with mode: 0644]
opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/TopologyUserLinkConfig.java [new file with mode: 0644]
opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/internal/Activator.java [new file with mode: 0644]
opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/internal/TopologyManagerImpl.java [new file with mode: 0644]
opendaylight/topologymanager/src/test/java/org/opendaylight/controller/topologymanager/internal/TopologyManagerImplTest.java [new file with mode: 0644]
opendaylight/usermanager/pom.xml [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/AuthResponse.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/IAAAProvider.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/ISessionManager.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/IUserManager.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/ODLUserLevel.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/Activator.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/AuthenticatedUser.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/AuthorizationConfig.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/ServerConfig.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/UserConfig.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/UserManagerImpl.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/security/SessionManager.java [new file with mode: 0644]
opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/security/UserSecurityContextRepository.java [new file with mode: 0644]
opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/AuthResponseTest.java [new file with mode: 0644]
opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/AuthenticatedUserTest.java [new file with mode: 0644]
opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/AuthorizationUserConfigTest.java [new file with mode: 0644]
opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/ServerConfigTest.java [new file with mode: 0644]
opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/UserManagerImplTest.java [new file with mode: 0644]
opendaylight/web/devices/pom.xml [new file with mode: 0644]
opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java [new file with mode: 0644]
opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/DevicesJsonBean.java [new file with mode: 0644]
opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/StatusJsonBean.java [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/WEB-INF/Devices-servlet.xml [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/web/devices/src/main/resources/js/page.js [new file with mode: 0644]
opendaylight/web/flows/pom.xml [new file with mode: 0644]
opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/WEB-INF/Flows-servlet.xml [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/web/flows/src/main/resources/js/page.js [new file with mode: 0644]
opendaylight/web/root/pom.xml [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/AuthenticationProviderWrapper.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerAuthenticationSuccessHandler.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerCustomFilter.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerLoginUrlAuthEntryPoint.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerLogoutHandler.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerUISessionManager.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerUserDetailsService.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerWebSecurityContextRepository.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/IOneWeb.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/OneWeb.java [new file with mode: 0644]
opendaylight/web/root/src/main/java/org/opendaylight/controller/web/OneWebAdmin.java [new file with mode: 0644]
opendaylight/web/root/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/web/root/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/web/root/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/web/root/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/web/root/src/main/resources/WEB-INF/RootGUI-servlet.xml [new file with mode: 0644]
opendaylight/web/root/src/main/resources/WEB-INF/jsp/login.jsp [new file with mode: 0644]
opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp [new file with mode: 0644]
opendaylight/web/root/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/web/root/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/web/root/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/web/root/src/main/resources/css/bootstrap.min.css [new file with mode: 0644]
opendaylight/web/root/src/main/resources/css/login.less [new file with mode: 0644]
opendaylight/web/root/src/main/resources/css/one.less [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/Device_pc_3045_default_64.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/Device_switch_3062_unknown_64.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/Expand16T.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/Key_0024_16.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/alert_unreachable_2008_128.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/logo.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/open_1054_16.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/open_1054_24.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/save_as_0106_16.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/save_as_0106_24.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/topology_view_1033_128.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/user_0020_16.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/user_0020_24.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/user_group_0107_16.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/img/user_group_0107_24.png [new file with mode: 0644]
opendaylight/web/root/src/main/resources/js/bootstrap.min.js [new file with mode: 0644]
opendaylight/web/root/src/main/resources/js/jit.js [new file with mode: 0644]
opendaylight/web/root/src/main/resources/js/jquery-1.9.1.min.js [new file with mode: 0644]
opendaylight/web/root/src/main/resources/js/less-1.3.3.min.js [new file with mode: 0644]
opendaylight/web/root/src/main/resources/js/one-topology.js [new file with mode: 0644]
opendaylight/web/root/src/main/resources/js/one.js [new file with mode: 0644]
opendaylight/web/topology/pom.xml [new file with mode: 0644]
opendaylight/web/topology/src/main/java/org/opendaylight/controller/topology/web/Topology.java [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/WEB-INF/Topology-servlet.xml [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/web/topology/src/main/resources/js/page.js [new file with mode: 0644]
opendaylight/web/topology/src/test/java/org/opendaylight/controller/topology/web/TopologyTest.java [new file with mode: 0644]
opendaylight/web/troubleshoot/pom.xml [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/TroubleshootingJsonBean.java [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/WEB-INF/Troubleshoot-servlet.xml [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/WEB-INF/spring/context.xml [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/WEB-INF/spring/servlet/security.xml [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
opendaylight/web/troubleshoot/src/main/resources/js/page.js [new file with mode: 0644]

diff --git a/opendaylight/arphandler/pom.xml b/opendaylight/arphandler/pom.xml
new file mode 100644 (file)
index 0000000..cf3aff7
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>arphandler</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+       <artifactId>maven-bundle-plugin</artifactId>
+       <version>2.3.6</version>
+       <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Import-Package>
+                 org.opendaylight.controller.sal.core,
+             org.opendaylight.controller.sal.utils,
+             org.opendaylight.controller.sal.packet,
+          org.opendaylight.controller.switchmanager,
+          org.opendaylight.controller.hosttracker,
+             org.opendaylight.controller.hosttracker.hostAware,
+          org.apache.felix.dm,
+          org.osgi.service.component,
+             org.slf4j
+           </Import-Package>
+        <Bundle-Activator>
+          org.opendaylight.controller.arphandler.internal.Activator
+               </Bundle-Activator>
+         </instructions>
+       </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>hosttracker</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/arphandler/src/main/java/org/opendaylight/controller/arphandler/internal/Activator.java b/opendaylight/arphandler/src/main/java/org/opendaylight/controller/arphandler/internal/Activator.java
new file mode 100644 (file)
index 0000000..e886813
--- /dev/null
@@ -0,0 +1,104 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.arphandler.internal;
+
+import java.util.Hashtable;
+import java.util.Dictionary;
+import org.apache.felix.dm.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.hosttracker.IfHostListener;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { ArpHandler.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(ArpHandler.class)) {
+            // export the service
+            Dictionary<String, String> props = new Hashtable<String, String>();
+            props.put("salListenerName", "arphandler");
+            c.setInterface(new String[] { IHostFinder.class.getName(),
+                    IListenDataPacket.class.getName() }, props);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManager.class).setCallbacks("setSwitchManager",
+                    "unsetSwitchManager").setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IDataPacketService.class).setCallbacks(
+                    "setDataPacketService", "unsetDataPacketService")
+                    .setRequired(true));
+
+            // the Host Listener is optional
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IfHostListener.class).setCallbacks("setHostListener",
+                    "unsetHostListener").setRequired(false));
+
+            // the IfIptoHost is a required dependency
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IfIptoHost.class).setCallbacks("setHostTracker",
+                    "unsetHostTracker").setRequired(true));
+        }
+    }
+}
diff --git a/opendaylight/arphandler/src/main/java/org/opendaylight/controller/arphandler/internal/ArpHandler.java b/opendaylight/arphandler/src/main/java/org/opendaylight/controller/arphandler/internal/ArpHandler.java
new file mode 100644 (file)
index 0000000..a9667d8
--- /dev/null
@@ -0,0 +1,506 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/*
+ *
+ */
+package org.opendaylight.controller.arphandler.internal;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.hosttracker.IfHostListener;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.ARP;
+import org.opendaylight.controller.sal.packet.BitBufferHelper;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.IPv4;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.opendaylight.controller.sal.packet.PacketResult;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.NetUtils;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.Subnet;
+
+public class ArpHandler implements IHostFinder, IListenDataPacket {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ArpHandler.class);
+    private IfIptoHost hostTracker = null;
+    private ISwitchManager switchManager = null;
+    private IDataPacketService dataPacketService = null;
+    private Set<IfHostListener> hostListener = Collections
+            .synchronizedSet(new HashSet<IfHostListener>());
+
+    void setHostListener(IfHostListener s) {
+        if (this.hostListener != null) {
+            this.hostListener.add(s);
+        }
+    }
+
+    void unsetHostListener(IfHostListener s) {
+        if (this.hostListener != null) {
+            this.hostListener.remove(s);
+        }
+    }
+
+    void setDataPacketService(IDataPacketService s) {
+        this.dataPacketService = s;
+    }
+
+    void unsetDataPacketService(IDataPacketService s) {
+        if (this.dataPacketService == s) {
+            this.dataPacketService = s;
+        }
+    }
+
+    public IfIptoHost getHostTracker() {
+        return hostTracker;
+    }
+
+    public void setHostTracker(IfIptoHost hostTracker) {
+        logger.debug("Setting HostTracker");
+        this.hostTracker = hostTracker;
+    }
+
+    public void unsetHostTracker(IfIptoHost s) {
+        logger.debug("UNSetting HostTracker");
+        if (this.hostTracker == s) {
+            this.hostTracker = null;
+        }
+    }
+
+    protected void sendARPReply(NodeConnector p, byte[] sMAC, InetAddress sIP,
+            byte[] tMAC, InetAddress tIP) {
+        byte[] senderIP = sIP.getAddress();
+        byte[] targetIP = tIP.getAddress();
+        ARP arp = new ARP();
+        arp.setHardwareType(ARP.HW_TYPE_ETHERNET).setProtocolType(
+                EtherTypes.IPv4.shortValue())
+                .setHardwareAddressLength((byte) 6).setProtocolAddressLength(
+                        (byte) 4).setOpCode(ARP.REPLY)
+                .setSenderHardwareAddress(sMAC).setSenderProtocolAddress(
+                        senderIP).setTargetHardwareAddress(tMAC)
+                .setTargetProtocolAddress(targetIP);
+
+        Ethernet ethernet = new Ethernet();
+        ethernet.setSourceMACAddress(sMAC).setDestinationMACAddress(tMAC)
+                .setEtherType(EtherTypes.ARP.shortValue()).setPayload(arp);
+
+        RawPacket destPkt = this.dataPacketService.encodeDataPacket(ethernet);
+        destPkt.setOutgoingNodeConnector(p);
+
+        this.dataPacketService.transmitDataPacket(destPkt);
+    }
+
+    private boolean isBroadcastMAC(byte[] mac) {
+        if (BitBufferHelper.toNumber(mac) == 0xffffffffffffL) { //TODO: implement this in our Ethernet
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isUnicastMAC(byte[] mac) {
+        if ((BitBufferHelper.toNumber(mac) & 0x010000000000L) == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    protected void handleARPPacket(Ethernet eHeader, ARP pkt, NodeConnector p) {
+        if (pkt.getOpCode() == 0x1) {
+            logger.debug("Received ARP REQUEST Packet from NodeConnector:" + p);
+        } else {
+            logger.debug("Received ARP REPLY Packet from NodeConnector:" + p);
+        }
+        InetAddress targetIP = null;
+        try {
+            targetIP = InetAddress.getByAddress(pkt.getTargetProtocolAddress());
+        } catch (UnknownHostException e1) {
+            return;
+        }
+        InetAddress sourceIP = null;
+        try {
+            sourceIP = InetAddress.getByAddress(pkt.getSenderProtocolAddress());
+        } catch (UnknownHostException e1) {
+            return;
+        }
+        byte[] targetMAC = eHeader.getDestinationMACAddress();
+        byte[] sourceMAC = eHeader.getSourceMACAddress();
+
+        /*
+         * Sanity Check; drop ARP packets originated by the controller itself.
+         * This is to avoid continuous flooding
+         */
+        if (Arrays.equals(sourceMAC, getControllerMAC())) {
+            logger.debug(
+                    "Receive the self originated packet (srcMAC {}) --> DROP",
+                    HexEncode.bytesToHexString(sourceMAC));
+            return;
+        }
+
+        Subnet subnet = null;
+        if (switchManager != null) {
+            subnet = switchManager.getSubnetByNetworkAddress(sourceIP);
+        }
+        if (subnet == null) {
+            logger.debug("can't find subnet matching {}, drop packet", sourceIP
+                    .toString());
+            return;
+        }
+        logger.debug("Found {} matching {}", subnet.toString(), sourceIP
+                .toString());
+        /*
+         * Make sure that the host is a legitimate member of this subnet
+         */
+        if (!subnet.hasNodeConnector(p)) {
+            logger.debug("{} showing up on {} does not belong to {}",
+                    new Object[] { sourceIP.toString(), p, subnet.toString() });
+            return;
+        }
+
+        if (isUnicastMAC(sourceMAC)) {
+            // TODO For not this is only OPENFLOW but we need to fix this
+            if (p.getType().equals(
+                    NodeConnector.NodeConnectorIDType.OPENFLOW)) {
+                HostNodeConnector host = null;
+                try {
+                    host = new HostNodeConnector(sourceMAC, sourceIP, p, subnet
+                            .getVlan());
+                } catch (ConstructionException e) {
+                    return;
+                }
+                /*
+                 * Learn host from the received ARP REQ/REPLY, inform
+                 * Host Tracker
+                 */
+                logger.debug("Inform Host tracker of new host {}", host);
+                synchronized (this.hostListener) {
+                    for (IfHostListener listener : this.hostListener) {
+                        listener.hostListener(host);
+                    }
+                }
+            }
+        }
+        /*
+         * No further action is needed if this is a gratuitous ARP
+         */
+        if (sourceIP.equals(targetIP)) {
+            return;
+        }
+
+        /*
+         * No further action is needed if this is a ARP Reply
+         */
+        if (pkt.getOpCode() != ARP.REQUEST) {
+            return;
+        }
+        /*
+         * ARP Request Handling:
+         * If targetIP is the IP of the subnet, reply with ARP REPLY
+         * If targetIP is a known host, PROXY ARP (by sending ARP REPLY) on behalf of known target hosts.
+         * For unknown target hosts, generate and send an ARP request to ALL switches/ports using
+         * the IP address defined in the subnet as source address
+         */
+        /*
+         * Send ARP reply if target IP is gateway IP
+         */
+        if ((targetIP.equals(subnet.getNetworkAddress()))
+                && (isBroadcastMAC(targetMAC) || Arrays.equals(targetMAC,
+                        getControllerMAC()))) {
+            sendARPReply(p, getControllerMAC(), targetIP, pkt
+                    .getSenderHardwareAddress(), sourceIP);
+            return;
+        }
+
+        /*
+         * unknown host, initiate ARP request
+         */
+        HostNodeConnector host = hostTracker.hostQuery(targetIP);
+        if (host == null) {
+            sendBcastARPRequest(targetIP, subnet);
+            return;
+        }
+        /*
+         * Known target host, send ARP REPLY
+         * make sure that targetMAC matches the host's MAC if it is not broadcastMAC
+         */
+        if (isBroadcastMAC(targetMAC)
+                || Arrays.equals(host.getDataLayerAddressBytes(), targetMAC)) {
+            sendARPReply(p, host.getDataLayerAddressBytes(), host
+                    .getNetworkAddress(), pkt.getSenderHardwareAddress(),
+                    sourceIP);
+            return;
+        } else {
+            /*
+             * target target MAC has been changed. For now, discard it.
+             * TODO: We may need to send unicast ARP REQUEST on behalf of the
+             * target back to the sender to trigger the sender to
+             * update its table
+             */
+            return;
+        }
+    }
+
+    /*
+     *  Send a broadcast ARP Request to the switch/ ports  using
+     *  the networkAddress of the subnet as sender IP
+     *  the controller's MAC as sender MAC
+     *  the targetIP as the target Network Address
+     */
+    protected void sendBcastARPRequest(InetAddress targetIP, Subnet subnet) {
+        Set<NodeConnector> nodeConnectors;
+        if (subnet.isFlatLayer2()) {
+            nodeConnectors = new HashSet<NodeConnector>();
+            for (Node n : this.switchManager.getNodes()) {
+                nodeConnectors.addAll(this.switchManager
+                        .getUpNodeConnectors(n));
+            }
+        } else {
+            nodeConnectors = subnet.getNodeConnectors();
+        }
+        for (NodeConnector p : nodeConnectors) {
+            ARP arp = new ARP();
+            byte[] senderIP = subnet.getNetworkAddress().getAddress();
+            byte[] targetIPB = targetIP.getAddress();
+            arp.setHardwareType(ARP.HW_TYPE_ETHERNET).setProtocolType(
+                    EtherTypes.IPv4.shortValue()).setHardwareAddressLength(
+                    (byte) 6).setProtocolAddressLength((byte) 4).setOpCode(
+                    ARP.REQUEST).setSenderHardwareAddress(getControllerMAC())
+                    .setSenderProtocolAddress(senderIP)
+                    .setTargetHardwareAddress(
+                            new byte[] { (byte) 0, (byte) 0, (byte) 0,
+                                    (byte) 0, (byte) 0, (byte) 0 })
+                    .setTargetProtocolAddress(targetIPB);
+
+            Ethernet ethernet = new Ethernet();
+            ethernet.setSourceMACAddress(getControllerMAC())
+                    .setDestinationMACAddress(
+                            new byte[] { (byte) -1, (byte) -1, (byte) -1,
+                                    (byte) -1, (byte) -1, (byte) -1 })
+                    .setEtherType(EtherTypes.ARP.shortValue()).setPayload(arp);
+
+            // TODO For now send port-by-port, see how to optimize to
+            // send to a bunch of port on the same node in a shoot
+            RawPacket destPkt = this.dataPacketService
+                    .encodeDataPacket(ethernet);
+            destPkt.setOutgoingNodeConnector(p);
+
+            this.dataPacketService.transmitDataPacket(destPkt);
+        }
+    }
+
+    /*
+     * Send a unicast ARP Request to the known host on a specific switch/port as
+     * defined in the host.
+     * The sender IP is the networkAddress of the subnet
+     * The sender MAC is the controller's MAC
+     */
+    protected void sendUcastARPRequest(HostNodeConnector host, Subnet subnet) {
+        //Long swID = host.getnodeconnectornodeId();
+        //Short portID = host.getnodeconnectorportId();
+        //Node n = NodeCreator.createOFNode(swID);
+        Node n = host.getnodeconnectorNode();
+        if (n == null) {
+            logger.error("cannot send UcastARP because cannot extract node "
+                    + "from HostNodeConnector:" + host);
+            return;
+        }
+        NodeConnector outPort = host.getnodeConnector();
+        if (outPort == null) {
+            logger.error("cannot send UcastARP because cannot extract "
+                    + "outPort from HostNodeConnector:" + host);
+            return;
+        }
+
+        byte[] senderIP = subnet.getNetworkAddress().getAddress();
+        byte[] targetIP = host.getNetworkAddress().getAddress();
+        byte[] targetMAC = host.getDataLayerAddressBytes();
+        ARP arp = new ARP();
+        arp.setHardwareType(ARP.HW_TYPE_ETHERNET).setProtocolType(
+                EtherTypes.IPv4.shortValue())
+                .setHardwareAddressLength((byte) 6).setProtocolAddressLength(
+                        (byte) 4).setOpCode(ARP.REQUEST)
+                .setSenderHardwareAddress(getControllerMAC())
+                .setSenderProtocolAddress(senderIP).setTargetHardwareAddress(
+                        targetMAC).setTargetProtocolAddress(targetIP);
+
+        Ethernet ethernet = new Ethernet();
+        ethernet.setSourceMACAddress(getControllerMAC())
+                .setDestinationMACAddress(targetMAC).setEtherType(
+                        EtherTypes.ARP.shortValue()).setPayload(arp);
+
+        RawPacket destPkt = this.dataPacketService.encodeDataPacket(ethernet);
+        destPkt.setOutgoingNodeConnector(outPort);
+
+        this.dataPacketService.transmitDataPacket(destPkt);
+    }
+
+    public void find(InetAddress networkAddress) {
+        logger.debug("Received find IP {}", networkAddress.toString());
+
+        Subnet subnet = null;
+        if (switchManager != null) {
+            subnet = switchManager.getSubnetByNetworkAddress(networkAddress);
+        }
+        if (subnet == null) {
+            logger.debug("can't find subnet matching IP {}", networkAddress
+                    .toString());
+            return;
+        }
+        logger.debug("found subnet {}", subnet.toString());
+
+        // send a broadcast ARP Request to this interface
+        sendBcastARPRequest(networkAddress, subnet);
+    }
+
+    /*
+     * Probe the host by sending a unicast ARP Request to the host
+     */
+    public void probe(HostNodeConnector host) {
+        logger.debug("Received probe host {}", host);
+
+        Subnet subnet = null;
+        if (switchManager != null) {
+            subnet = switchManager.getSubnetByNetworkAddress(host
+                    .getNetworkAddress());
+        }
+        if (subnet == null) {
+            logger.debug("can't find subnet matching {}", host
+                    .getNetworkAddress().toString());
+            return;
+        }
+        sendUcastARPRequest(host, subnet);
+    }
+
+    /*
+     * An IP packet is punted to the controller, this means that the
+     * destination host is not known to the controller.
+     * Need to discover it by sending a Broadcast ARP Request
+     */
+    protected void handlePuntedIPPacket(IPv4 pkt, NodeConnector p) {
+        InetAddress dIP = null;
+        try {
+            dIP = InetAddress.getByAddress(NetUtils.intToByteArray4(pkt
+                    .getDestinationAddress()));
+        } catch (UnknownHostException e1) {
+            return;
+        }
+
+        Subnet subnet = null;
+        if (switchManager != null) {
+            subnet = switchManager.getSubnetByNetworkAddress(dIP);
+        }
+        if (subnet == null) {
+            logger.debug("can't find subnet matching {}, drop packet", dIP
+                    .toString());
+            return;
+        }
+        logger.debug("Found {} matching {}", subnet.toString(), dIP.toString());
+        /*
+         * unknown destination host, initiate ARP request
+         */
+        sendBcastARPRequest(dIP, subnet);
+        return;
+    }
+
+    public byte[] getControllerMAC() {
+        if (switchManager == null) {
+            return null;
+        }
+        return switchManager.getControllerMAC();
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    void setSwitchManager(ISwitchManager s) {
+        logger.debug("SwitchManager set");
+        this.switchManager = s;
+    }
+
+    void unsetSwitchManager(ISwitchManager s) {
+        if (this.switchManager == s) {
+            logger.debug("SwitchManager removed!");
+            this.switchManager = null;
+        }
+    }
+
+    @Override
+    public PacketResult receiveDataPacket(RawPacket inPkt) {
+        if (inPkt == null) {
+            return PacketResult.IGNORED;
+        }
+        logger
+                .trace("Received a frame of size:"
+                        + inPkt.getPacketData().length);
+        Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
+        if (formattedPak instanceof Ethernet) {
+            Object nextPak = formattedPak.getPayload();
+            if (nextPak instanceof IPv4) {
+                handlePuntedIPPacket((IPv4) nextPak, inPkt
+                        .getIncomingNodeConnector());
+                logger.trace("Handled IP packet");
+            }
+            if (nextPak instanceof ARP) {
+                handleARPPacket((Ethernet) formattedPak, (ARP) nextPak, inPkt
+                        .getIncomingNodeConnector());
+                logger.trace("Handled ARP packet");
+            }
+        }
+        return PacketResult.IGNORED;
+    }
+}
diff --git a/opendaylight/arphandler/src/test/java/org/opendaylight/controller/arphandler/internal/ArphandlerTest.java b/opendaylight/arphandler/src/test/java/org/opendaylight/controller/arphandler/internal/ArphandlerTest.java
new file mode 100644 (file)
index 0000000..b863d2b
--- /dev/null
@@ -0,0 +1,52 @@
+\r
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.arphandler.internal;\r
+\r
+\r
+import org.junit.Assert;\r
+import org.junit.Test;\r
+import junit.framework.TestCase;\r
+\r
+import org.opendaylight.controller.hosttracker.IfIptoHost;\r
+import org.opendaylight.controller.hosttracker.HostTracker;\r
+\r
+import org.opendaylight.controller.switchmanager.ISwitchManager;\r
+import org.opendaylight.controller.switchmanager.internal.SwitchManagerImpl;\r
+\r
+\r
+public class ArphandlerTest extends TestCase {\r
+        \r
+       @Test\r
+       public void testArphandlerCreation() {\r
+                       \r
+               ArpHandler ah = null;\r
+               ah = new ArpHandler();\r
+               Assert.assertTrue(ah != null);\r
+                       \r
+               HostTracker hostTracker = null;\r
+               hostTracker = new HostTracker();\r
+               ah.setHostTracker(hostTracker);\r
+               IfIptoHost ht= ah.getHostTracker();\r
+               Assert.assertTrue(ht.equals(hostTracker));\r
+               ah.unsetHostTracker(hostTracker);\r
+               ht= ah.getHostTracker();\r
+               Assert.assertTrue(ht == null);\r
+               \r
+               ah.setHostListener(hostTracker);\r
+               ah.unsetHostListener(hostTracker);\r
+               \r
+               ISwitchManager swManager = new SwitchManagerImpl();\r
+               ah.setSwitchManager(swManager);\r
+               ah.unsetSwitchManager(swManager);\r
+               \r
+       }\r
+\r
+\r
+}\r
diff --git a/opendaylight/configuration/api/pom.xml b/opendaylight/configuration/api/pom.xml
new file mode 100644 (file)
index 0000000..0af960d
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>configuration</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Import-Package>
+              org.opendaylight.controller.sal.utils,
+              org.apache.commons.lang3.builder
+           </Import-Package>
+           <Export-Package>
+              org.opendaylight.controller.configuration
+           </Export-Package>
+         </instructions>
+       </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+       <dependency>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>sal</artifactId>
+               <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+  </dependencies>
+</project>
diff --git a/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationAware.java b/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationAware.java
new file mode 100644 (file)
index 0000000..69c2594
--- /dev/null
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration;
+
+
+/**
+ * Listener Interface for receiving Configuration events.
+ *
+ *
+ */
+public interface IConfigurationAware extends IConfigurationAwareCommon {
+}
diff --git a/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationAwareCommon.java b/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationAwareCommon.java
new file mode 100644 (file)
index 0000000..57a648b
--- /dev/null
@@ -0,0 +1,26 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration;
+
+import org.opendaylight.controller.sal.utils.Status;
+
+
+/**
+ * Listener Interface for receiving Configuration events.
+ *
+ *
+ */
+public interface IConfigurationAwareCommon {
+
+    /**
+     * Trigger from configuration component to persist the configuration state.
+     */
+    public Status saveConfiguration();
+}
diff --git a/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationContainerAware.java b/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationContainerAware.java
new file mode 100644 (file)
index 0000000..ac1aabe
--- /dev/null
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration;
+
+
+/**
+ * Listener Interface for receiving Configuration events.
+ *
+ *
+ */
+public interface IConfigurationContainerAware extends IConfigurationAwareCommon {
+}
diff --git a/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationContainerService.java b/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationContainerService.java
new file mode 100644 (file)
index 0000000..fb5be35
--- /dev/null
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration;
+
+/**
+ * Container Manager interface
+ *
+ *
+ */
+public interface IConfigurationContainerService extends
+        IConfigurationServiceCommon {
+}
diff --git a/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationService.java b/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationService.java
new file mode 100644 (file)
index 0000000..c6a0183
--- /dev/null
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration;
+
+
+/**
+ * Container Manager interface
+ *
+ *
+ */
+public interface IConfigurationService extends IConfigurationServiceCommon {
+}
diff --git a/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationServiceCommon.java b/opendaylight/configuration/api/src/main/java/org/opendaylight/controller/configuration/IConfigurationServiceCommon.java
new file mode 100644 (file)
index 0000000..fc9c5ac
--- /dev/null
@@ -0,0 +1,21 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration;
+
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * Container Manager interface
+ *
+ *
+ */
+public interface IConfigurationServiceCommon {
+    public Status saveConfigurations();
+}
diff --git a/opendaylight/configuration/implementation/pom.xml b/opendaylight/configuration/implementation/pom.xml
new file mode 100644 (file)
index 0000000..bac0184
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>configuration.implementation</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.controller.configuration,
+              org.opendaylight.controller.clustering.services,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.sal.core,
+              org.osgi.framework,
+              org.slf4j,
+              org.apache.felix.dm
+            </Import-Package>
+            <Export-Package>
+            </Export-Package>
+            <Bundle-Activator>
+              org.opendaylight.controller.configuration.internal.Activator
+            </Bundle-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>configuration</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>sal</artifactId>
+               <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+  </dependencies>
+</project>
diff --git a/opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/Activator.java b/opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/Activator.java
new file mode 100644 (file)
index 0000000..9fb5b15
--- /dev/null
@@ -0,0 +1,137 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.configuration.IConfigurationAware;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.configuration.IConfigurationContainerService;
+import org.opendaylight.controller.configuration.IConfigurationService;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @file Activator.java
+ *
+ * @brief Component Activator for Configuration Management.
+ *
+ *
+ *
+ */
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { ConfigurationContainerImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(ConfigurationContainerImpl.class)) {
+            // export the service
+            c.setInterface(new String[] {
+                    IConfigurationContainerService.class.getName(),
+                    IConfigurationAware.class.getName() }, null);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IConfigurationContainerAware.class).setCallbacks(
+                    "addConfigurationContainerAware",
+                    "removeConfigurationContainerAware").setRequired(false));
+        }
+    }
+
+    /**
+     * Method which tells how many Global implementations are
+     * supported by the bundle. This way we can tune the number of
+     * components created. This components will be created ONLY at the
+     * time of bundle startup and will be destroyed only at time of
+     * bundle destruction, this is the major difference with the
+     * implementation retrieved via getImplementations where all of
+     * them are assumed to be in a container!
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * in Global version
+     */
+    protected Object[] getGlobalImplementations() {
+        Object[] res = { ConfigurationImpl.class };
+        return res;
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureGlobalInstance(Component c, Object imp) {
+        if (imp.equals(ConfigurationImpl.class)) {
+
+            // export the service
+            c.setInterface(
+                    new String[] { IConfigurationService.class.getName() },
+                    null);
+
+            c.add(createServiceDependency().setService(
+                    IClusterGlobalServices.class).setCallbacks(
+                    "setClusterServices", "unsetClusterServices").setRequired(
+                    true));
+
+            c.add(createServiceDependency().setService(
+                    IConfigurationAware.class).setCallbacks(
+                    "addConfigurationAware", "removeConfigurationAware")
+                    .setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerImpl.java b/opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerImpl.java
new file mode 100644 (file)
index 0000000..efe966b
--- /dev/null
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.configuration.IConfigurationAware;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.configuration.IConfigurationContainerService;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.Status;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @file   ConfigurationImpl.java
+ *
+ * @brief  Backend functionality for all Configuration related tasks.
+ *
+ *
+ */
+
+public class ConfigurationContainerImpl implements
+        IConfigurationContainerService, IConfigurationAware {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ConfigurationContainerImpl.class);
+    private IClusterGlobalServices clusterServices;
+    /*
+     * Collection containing the configuration objects.
+     * This is configuration world: container names (also the map key)
+     * are maintained as they were configured by user, same case
+     */
+    private Set<IConfigurationContainerAware> configurationAwareList = (Set<IConfigurationContainerAware>) Collections
+            .synchronizedSet(new HashSet<IConfigurationContainerAware>());
+
+    public void addConfigurationContainerAware(
+            IConfigurationContainerAware configurationAware) {
+        if (!this.configurationAwareList.contains(configurationAware)) {
+            this.configurationAwareList.add(configurationAware);
+        }
+    }
+
+    public int getConfigurationAwareListSize() {
+       return this.configurationAwareList.size();
+    }
+    
+    public void removeConfigurationContainerAware(
+            IConfigurationContainerAware configurationAware) {
+        this.configurationAwareList.remove(configurationAware);
+    }
+
+    public void setClusterServices(IClusterGlobalServices i) {
+        this.clusterServices = i;
+        logger.debug("IClusterServices set");
+    }
+
+    public void unsetClusterServices(IClusterGlobalServices i) {
+        if (this.clusterServices == i) {
+            this.clusterServices = null;
+            logger.debug("IClusterServices Unset");
+        }
+    }
+
+    public void init() {
+    }
+
+    public void destroy() {
+        // Clear local states
+        this.configurationAwareList.clear();
+    }
+
+    @Override
+    public Status saveConfiguration() {
+        boolean success = true;
+        for (IConfigurationContainerAware configurationAware : configurationAwareList) {
+            logger.info("Save Config triggered for "
+                    + configurationAware.getClass().getSimpleName());
+
+            Status status = configurationAware.saveConfiguration();
+            if (!status.isSuccess()) {
+               success = false;
+               logger.info("Failed to save config for "
+                               + configurationAware.getClass().getSimpleName());
+            }
+        }
+        if (success) {
+            return new Status(StatusCode.SUCCESS, null);
+        } else {
+            return new Status(StatusCode.INTERNALERROR,
+                       "Failed to Save All Configurations");
+        }
+    }
+
+    @Override
+    public Status saveConfigurations() {
+        return saveConfiguration();
+    }
+}
diff --git a/opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/ConfigurationImpl.java b/opendaylight/configuration/implementation/src/main/java/org/opendaylight/controller/configuration/internal/ConfigurationImpl.java
new file mode 100644 (file)
index 0000000..a8f7da6
--- /dev/null
@@ -0,0 +1,99 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.configuration.IConfigurationAware;
+import org.opendaylight.controller.configuration.IConfigurationService;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.Status;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @file   ConfigurationImpl.java
+ *
+ * @brief  Backend functionality for all Configuration related tasks.
+ *
+ *
+ */
+
+public class ConfigurationImpl implements IConfigurationService {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ConfigurationImpl.class);
+    private IClusterGlobalServices clusterServices;
+    /*
+     * Collection containing the configuration objects.
+     * This is configuration world: container names (also the map key)
+     * are maintained as they were configured by user, same case
+     */
+    private Set<IConfigurationAware> configurationAwareList = (Set<IConfigurationAware>) Collections
+            .synchronizedSet(new HashSet<IConfigurationAware>());
+
+    
+    public int getConfigurationAwareListSize() {
+       return this.configurationAwareList.size();
+    }
+    
+    public void addConfigurationAware(IConfigurationAware configurationAware) {
+        if (!this.configurationAwareList.contains(configurationAware)) {
+            this.configurationAwareList.add(configurationAware);
+        }
+    }
+
+    public void removeConfigurationAware(IConfigurationAware configurationAware) {
+        this.configurationAwareList.remove(configurationAware);
+    }
+
+    public void setClusterServices(IClusterGlobalServices i) {
+        this.clusterServices = i;
+        logger.debug("IClusterServices set");
+    }
+
+    public void unsetClusterServices(IClusterGlobalServices i) {
+        if (this.clusterServices == i) {
+            this.clusterServices = null;
+            logger.debug("IClusterServices Unset");
+        }
+    }
+
+    public void init() {
+        logger.info("ContainerManager startup....");
+    }
+
+    public void destroy() {
+        // Clear local states
+        this.configurationAwareList.clear();
+    }
+
+    @Override
+    public Status saveConfigurations() {
+        boolean success = true;
+        for (IConfigurationAware configurationAware : configurationAwareList) {
+               Status status = configurationAware.saveConfiguration();
+            if (!status.isSuccess()) {
+               success = false;
+               logger.info("Failed to save config for "
+                               + configurationAware.getClass().getName());
+            }
+        }
+        if (success) {
+            return new Status(StatusCode.SUCCESS, null);
+        } else {
+            return new Status(StatusCode.INTERNALERROR,
+                       "Failed to Save All Configurations");
+        }
+    }
+
+}
diff --git a/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationAwareTest.java b/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationAwareTest.java
new file mode 100644 (file)
index 0000000..e93b467
--- /dev/null
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import org.opendaylight.controller.configuration.IConfigurationAware;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * @file   TestConfigurationAware.java
+ *
+ * @brief  Test Class to create for jUnit test cases for Configuration Implementation
+ *
+ *
+ */
+
+public class ConfigurationAwareTest implements
+IConfigurationAware {
+       
+       @Override
+       public Status saveConfiguration() {
+               return null;
+       }
+
+
+}
diff --git a/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerAwareTest.java b/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerAwareTest.java
new file mode 100644 (file)
index 0000000..52f3776
--- /dev/null
@@ -0,0 +1,35 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * @file   TestConfigurationAware.java
+ *
+ * @brief  Test Class to create for jUnit test cases for Configuration Implementation
+ *
+ *
+ */
+
+public class ConfigurationContainerAwareTest implements
+         IConfigurationContainerAware {
+
+       
+       
+       
+       @Override
+       public Status saveConfiguration() {
+               return null;
+       }
+       
+       
+}
diff --git a/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerImplTest.java b/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationContainerImplTest.java
new file mode 100644 (file)
index 0000000..1d704a1
--- /dev/null
@@ -0,0 +1,65 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import org.junit.*;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+
+
+
+public class ConfigurationContainerImplTest { 
+       
+
+       @Test
+       public void testAddRemoveSaveConfiguration() {
+               
+               ConfigurationContainerImpl configurationContainerImpl = new ConfigurationContainerImpl();
+               IConfigurationContainerAware testConfigurationContainerAware = new ConfigurationContainerAwareTest();
+               
+               configurationContainerImpl.addConfigurationContainerAware(testConfigurationContainerAware);
+               configurationContainerImpl.addConfigurationContainerAware(testConfigurationContainerAware);
+               
+               Assert.assertEquals(1, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               IConfigurationContainerAware testConfigurationAware1 = new ConfigurationContainerAwareTest();
+               configurationContainerImpl.addConfigurationContainerAware(testConfigurationAware1);
+               
+               Assert.assertEquals(2, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               IConfigurationContainerAware testConfigurationAware2 = new ConfigurationContainerAwareTest();
+               configurationContainerImpl.addConfigurationContainerAware(testConfigurationAware2);
+               
+               Assert.assertEquals(3, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               IConfigurationContainerAware testConfigurationAware3 = new ConfigurationContainerAwareTest();
+               configurationContainerImpl.addConfigurationContainerAware(testConfigurationAware3);
+               
+               Assert.assertEquals(4, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               configurationContainerImpl.removeConfigurationContainerAware(testConfigurationContainerAware);
+               Assert.assertEquals(3, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               configurationContainerImpl.removeConfigurationContainerAware(testConfigurationContainerAware);
+               Assert.assertEquals(3, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               configurationContainerImpl.removeConfigurationContainerAware(testConfigurationAware3);
+               Assert.assertEquals(2, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               configurationContainerImpl.removeConfigurationContainerAware(testConfigurationAware2);
+               Assert.assertEquals(1, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               configurationContainerImpl.removeConfigurationContainerAware(testConfigurationAware1);
+               Assert.assertEquals(0, configurationContainerImpl.getConfigurationAwareListSize());
+               
+               
+       }
+       
+}
+
diff --git a/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationImplTest.java b/opendaylight/configuration/implementation/src/test/java/org/opendaylight/controller/configuration/internal/ConfigurationImplTest.java
new file mode 100644 (file)
index 0000000..71d768d
--- /dev/null
@@ -0,0 +1,63 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.configuration.internal;
+
+import org.junit.*;
+import org.opendaylight.controller.configuration.IConfigurationAware;
+
+public class ConfigurationImplTest { 
+       
+
+       @Test
+       public void testAddRemoveSaveConfiguration() {
+               
+               ConfigurationImpl configurationImpl = new ConfigurationImpl();
+               IConfigurationAware testConfigurationAware = new ConfigurationAwareTest();
+               
+               configurationImpl.addConfigurationAware(testConfigurationAware);
+               configurationImpl.addConfigurationAware(testConfigurationAware);
+               
+               Assert.assertEquals(1, configurationImpl.getConfigurationAwareListSize());
+               
+               ConfigurationAwareTest testConfigurationAware1 = new ConfigurationAwareTest();
+               configurationImpl.addConfigurationAware(testConfigurationAware1);
+               
+               Assert.assertEquals(2, configurationImpl.getConfigurationAwareListSize());
+               
+               ConfigurationAwareTest testConfigurationAware2 = new ConfigurationAwareTest();
+               configurationImpl.addConfigurationAware(testConfigurationAware2);
+               
+               Assert.assertEquals(3, configurationImpl.getConfigurationAwareListSize());
+               
+               ConfigurationAwareTest testConfigurationAware3 = new ConfigurationAwareTest();
+               configurationImpl.addConfigurationAware(testConfigurationAware3);
+               
+               Assert.assertEquals(4, configurationImpl.getConfigurationAwareListSize());
+               
+               
+               configurationImpl.removeConfigurationAware(testConfigurationAware);
+               Assert.assertEquals(3, configurationImpl.getConfigurationAwareListSize());
+               
+               configurationImpl.removeConfigurationAware(testConfigurationAware);
+               Assert.assertEquals(3, configurationImpl.getConfigurationAwareListSize());
+               
+               configurationImpl.removeConfigurationAware(testConfigurationAware3);
+               Assert.assertEquals(2, configurationImpl.getConfigurationAwareListSize());
+               
+               configurationImpl.removeConfigurationAware(testConfigurationAware1);
+               Assert.assertEquals(1, configurationImpl.getConfigurationAwareListSize());
+               
+               configurationImpl.removeConfigurationAware(testConfigurationAware2);
+               Assert.assertEquals(0, configurationImpl.getConfigurationAwareListSize());
+               
+       }
+       
+}
+
diff --git a/opendaylight/containermanager/api/pom.xml b/opendaylight/containermanager/api/pom.xml
new file mode 100644 (file)
index 0000000..8b5ccdf
--- /dev/null
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>containermanager</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Import-Package>
+                 org.opendaylight.controller.sal.action,
+                 org.opendaylight.controller.sal.authorization,
+                 org.opendaylight.controller.sal.core,
+                 org.opendaylight.controller.sal.flowprogrammer,
+                 org.opendaylight.controller.sal.match,
+              org.opendaylight.controller.sal.reader,
+              org.opendaylight.controller.sal.utils,
+              org.apache.commons.lang3.builder
+           </Import-Package>
+           <Export-Package>
+              org.opendaylight.controller.containermanager
+           </Export-Package>
+         </instructions>
+       </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+       <dependency>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>sal</artifactId>
+               <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+  </dependencies>
+</project>
diff --git a/opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/IContainerAuthorization.java b/opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/IContainerAuthorization.java
new file mode 100644 (file)
index 0000000..6d3d9f8
--- /dev/null
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.containermanager;
+
+import org.opendaylight.controller.sal.authorization.IResourceAuthorization;
+
+/**
+ * Container groups and container users authorizations
+ */
+public interface IContainerAuthorization extends IResourceAuthorization {
+
+}
diff --git a/opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/IContainerManager.java b/opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/IContainerManager.java
new file mode 100644 (file)
index 0000000..f057959
--- /dev/null
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.containermanager;
+
+import java.util.List;
+
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * Container Manager interface
+ *
+ *
+ */
+public interface IContainerManager {
+
+    /**
+     * Returns a list of Containers that currently exist.
+     *
+     * @return array of String Container names
+     */
+    public boolean hasNonDefaultContainer();
+
+    /**
+     * Returns a list of Containers that currently exist.
+     *
+     * @return array of String Container names
+     */
+    public List<String> getContainerNames();
+
+    /**
+     * Save the current container configuration to disk.
+     * TODO : REMOVE THIS FUNCTION and make Save as a service rather than the
+     * current hack of calling individual save routines.
+     *
+     * @return status code
+     */
+    public Status saveContainerConfig();
+}
diff --git a/opendaylight/containermanager/implementation/pom.xml b/opendaylight/containermanager/implementation/pom.xml
new file mode 100644 (file)
index 0000000..1ead516
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>containermanager.implementation</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.controller.containermanager,
+              org.opendaylight.controller.clustering.services,
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.action,
+              org.opendaylight.controller.sal.flowprogrammer,
+              org.opendaylight.controller.sal.match,
+              org.opendaylight.controller.sal.reader,
+              org.eclipse.osgi.framework.console,
+              org.osgi.framework,
+              org.slf4j,
+              org.apache.felix.dm
+            </Import-Package>
+            <Export-Package>
+            </Export-Package>
+            <Bundle-Activator>
+              org.opendaylight.controller.containermanager.internal.Activator
+            </Bundle-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+  </dependencies>
+</project>
diff --git a/opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/Activator.java b/opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/Activator.java
new file mode 100644 (file)
index 0000000..9caa620
--- /dev/null
@@ -0,0 +1,129 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.containermanager.internal;
+
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.containermanager.IContainerManager;
+import org.opendaylight.controller.sal.core.IContainerAware;
+import org.opendaylight.controller.sal.core.IContainer;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.apache.felix.dm.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { ContainerImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(ContainerImpl.class)) {
+            // export the service
+            c.setInterface(new String[] { IContainer.class.getName() }, null);
+        }
+    }
+
+    /**
+     * Method which tells how many Global implementations are
+     * supported by the bundle. This way we can tune the number of
+     * components created. This components will be created ONLY at the
+     * time of bundle startup and will be destroyed only at time of
+     * bundle destruction, this is the major difference with the
+     * implementation retrieved via getImplementations where all of
+     * them are assumed to be in a container!
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * in Global version
+     */
+    protected Object[] getGlobalImplementations() {
+        Object[] res = { ContainerManager.class };
+        return res;
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureGlobalInstance(Component c, Object imp) {
+        if (imp.equals(ContainerManager.class)) {
+
+            // export the service
+            c.setInterface(new String[] { IContainerManager.class.getName() },
+                    null);
+
+            c.add(createServiceDependency().setService(
+                    IClusterGlobalServices.class).setCallbacks(
+                    "setClusterServices", "unsetClusterServices").setRequired(
+                    true));
+
+            // Key kick-starter for container creation in each component
+            c.add(createServiceDependency().setService(IContainerAware.class)
+                    .setCallbacks("setIContainerAware", "unsetIContainerAware")
+                    .setRequired(false));
+
+            // Optional interface expected to be exported by the
+            // protocol plugins to setup proper filtering based on
+            // slicing events
+            c.add(createServiceDependency()
+                    .setService(IContainerListener.class).setCallbacks(
+                            "setIContainerListener", "unsetIContainerListener")
+                    .setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/ContainerImpl.java b/opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/ContainerImpl.java
new file mode 100644 (file)
index 0000000..15f939d
--- /dev/null
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * @file   ContainerImpl.java
+ *
+ * @brief  Class that instantiated per-container implements the
+ * interface IContainer
+ *
+ *
+ */
+package org.opendaylight.controller.containermanager.internal;
+
+import java.util.Dictionary;
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import java.util.Set;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import java.util.List;
+import org.opendaylight.controller.sal.core.IContainer;
+
+public class ContainerImpl implements IContainer {
+    private String containerName = null;
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init(Component c) {
+        Dictionary<?, ?> props = c.getServiceProperties();
+        if (props != null) {
+            this.containerName = (String) props.get("containerName");
+        }
+    }
+
+    @Override
+    public String getName() {
+        return this.containerName;
+    }
+
+    @Override
+    public List<ContainerFlow> getContainerFlows() {
+        return null;
+    }
+
+    @Override
+    public short getTag(Node n) {
+        return (short) 0;
+    }
+
+    @Override
+    public Set<NodeConnector> getNodeConnectors() {
+        return null;
+    }
+
+    @Override
+    public Set<Node> getNodes() {
+        return null;
+    }
+}
diff --git a/opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/ContainerManager.java b/opendaylight/containermanager/implementation/src/main/java/org/opendaylight/controller/containermanager/internal/ContainerManager.java
new file mode 100644 (file)
index 0000000..d47aca8
--- /dev/null
@@ -0,0 +1,126 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * @file   ContainerManager.java
+ *
+ * @brief  Manage one or many Containers
+ *
+ *
+ */
+package org.opendaylight.controller.containermanager.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.containermanager.IContainerManager;
+import org.opendaylight.controller.sal.core.IContainerAware;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.Status;
+
+public class ContainerManager implements IContainerManager {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ContainerManager.class);
+    private IClusterGlobalServices clusterServices;
+    /*
+     * Collection containing the configuration objects.
+     * This is configuration world: container names (also the map key)
+     * are maintained as they were configured by user, same case
+     */
+    private Set<IContainerAware> iContainerAware = (Set<IContainerAware>) Collections
+            .synchronizedSet(new HashSet<IContainerAware>());
+    private Set<IContainerListener> iContainerListener = Collections
+            .synchronizedSet(new HashSet<IContainerListener>());
+
+    void setIContainerListener(IContainerListener s) {
+        if (this.iContainerListener != null) {
+            this.iContainerListener.add(s);
+        }
+    }
+
+    void unsetIContainerListener(IContainerListener s) {
+        if (this.iContainerListener != null) {
+            this.iContainerListener.remove(s);
+        }
+    }
+
+    public void setIContainerAware(IContainerAware iContainerAware) {
+        if (!this.iContainerAware.contains(iContainerAware)) {
+            this.iContainerAware.add(iContainerAware);
+            // Now call the container creation for all the known containers so
+            // far
+            List<String> containerDB = getContainerNames();
+            if (containerDB != null) {
+                for (int i = 0; i < containerDB.size(); i++) {
+                    iContainerAware.containerCreate(containerDB.get(i));
+                }
+            }
+        }
+    }
+
+    public void unsetIContainerAware(IContainerAware iContainerAware) {
+        this.iContainerAware.remove(iContainerAware);
+        // There is no need to do cleanup of the component when
+        // unregister because it will be taken care by the Containerd
+        // component itself
+    }
+
+    public void setClusterServices(IClusterGlobalServices i) {
+        this.clusterServices = i;
+        logger.debug("IClusterServices set");
+    }
+
+    public void unsetClusterServices(IClusterGlobalServices i) {
+        if (this.clusterServices == i) {
+            this.clusterServices = null;
+            logger.debug("IClusterServices Unset");
+        }
+    }
+
+    public void init() {
+        logger.info("ContainerManager startup....");
+    }
+
+    public void destroy() {
+        // Clear local states
+        this.iContainerAware.clear();
+        this.iContainerListener.clear();
+
+        logger.info("ContainerManager Shutdown....");
+    }
+
+    @Override
+    public List<String> getContainerNames() {
+        /*
+         * Return container names as they were configured by user (case sensitive)
+         * along with the default container
+         */
+        List<String> containerNameList = new ArrayList<String>();
+        containerNameList.add(GlobalConstants.DEFAULT.toString());
+        return containerNameList;
+    }
+
+    @Override
+    public boolean hasNonDefaultContainer() {
+        return false;
+    }
+
+    @Override
+    public Status saveContainerConfig() {
+        return null;
+    }
+
+}
diff --git a/opendaylight/containermanager/implementation/src/test/java/org/opendaylight/controller/containermanager/internal/ContainerImplTest.java b/opendaylight/containermanager/implementation/src/test/java/org/opendaylight/controller/containermanager/internal/ContainerImplTest.java
new file mode 100644 (file)
index 0000000..4bdd679
--- /dev/null
@@ -0,0 +1,60 @@
+\r
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.containermanager.internal;\r
+\r
+import static org.junit.Assert.*;\r
+\r
+import java.util.Hashtable;\r
+\r
+import org.apache.felix.dm.impl.ComponentImpl;\r
+import org.junit.Test;\r
+import org.opendaylight.controller.sal.core.Node;\r
+import org.opendaylight.controller.sal.utils.NodeCreator;\r
+\r
+public class ContainerImplTest {\r
+\r
+       @Test\r
+       public void test() {\r
+               \r
+               ContainerImpl container1 = new ContainerImpl();\r
+                               \r
+               //Create Component for init\r
+               ComponentImpl component1 = new ComponentImpl(null, null, null);\r
+               component1.setInterface("serviceTestName", null);\r
+\r
+               //container1 does not have name yet\r
+               container1.init(component1);\r
+               assertNull(container1.getName());\r
+               \r
+               //Sets container1 name to TestName\r
+               Hashtable<String, String> properties = new Hashtable<String, String>();\r
+               properties.put("dummyKey", "dummyValue");\r
+               properties.put("containerName", "TestName");\r
+               component1.setInterface("serviceTestName", properties);\r
+\r
+               container1.init(component1);\r
+               assertEquals("TestName", container1.getName());\r
+               \r
+               //getContainerFlows always returns null for now\r
+               assertNull(container1.getContainerFlows());\r
+               \r
+               //getTag always returns 0 for now\r
+               Node n = NodeCreator.createOFNode(1L);\r
+               assertEquals(0, container1.getTag(n));\r
+               \r
+               //getNodeConnectors always returns null for now\r
+               assertNull(container1.getNodeConnectors());\r
+               \r
+               //getNodes always returns null for now\r
+               assertNull(container1.getNodes());\r
+               \r
+       }\r
+\r
+}\r
diff --git a/opendaylight/containermanager/implementation/src/test/java/org/opendaylight/controller/containermanager/internal/ContainerManagerTest.java b/opendaylight/containermanager/implementation/src/test/java/org/opendaylight/controller/containermanager/internal/ContainerManagerTest.java
new file mode 100644 (file)
index 0000000..b5e7a11
--- /dev/null
@@ -0,0 +1,38 @@
+\r
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.containermanager.internal;\r
+\r
+import static org.junit.Assert.*;\r
+\r
+import java.util.ArrayList;\r
+\r
+import org.junit.Test;\r
+import org.opendaylight.controller.sal.utils.GlobalConstants;\r
+\r
+public class ContainerManagerTest {\r
+\r
+       @Test\r
+       public void test() {\r
+               ContainerManager cm = new ContainerManager();\r
+               \r
+               cm.init();\r
+               \r
+               ArrayList<String> names = (ArrayList<String>) cm.getContainerNames();\r
+               assertEquals(1, names.size());\r
+               assertEquals(GlobalConstants.DEFAULT.toString(), names.get(0));\r
+               \r
+               assertFalse(cm.hasNonDefaultContainer());\r
+               assertNull(cm.saveContainerConfig());\r
+               \r
+               cm.destroy();\r
+\r
+       }\r
+\r
+}\r
index 3ec92d316e69df86d7e511beb4286c2b3d86ecb6..6984fd647b59c9599c3e00e44c07c6e293448f90 100644 (file)
   <version>0.1.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <modules>
+    <module>../../forwarding/staticrouting</module>
     <module>../../clustering/services</module>
     <module>../../clustering/services_implementation</module>
     <module>../../clustering/stub</module>
     <module>../../clustering/test</module>
+    <module>../../configuration/api</module>
+    <module>../../configuration/implementation</module>
+    <module>../../routing/dijkstra_implementation</module>
+    <module>../../arphandler</module>
+    <module>../../forwardingrulesmanager</module>
+    <module>../../hosttracker</module>
+    <module>../../containermanager/api</module>
+    <module>../../containermanager/implementation</module>
+    <module>../../switchmanager</module>
+    <module>../../statisticsmanager</module>
+    <module>../../topologymanager</module>
+    <module>../../usermanager</module>
     <module>../../../third-party/openflowj</module>
     <module>../../../third-party/net.sf.jung2</module>
     <module>../../../third-party/jersey-servlet</module>
     <!-- SAL bundles -->
     <module>../../sal/api</module>
     <module>../../sal/implementation</module>
+    
+    <!--  Web bundles -->
+    <module>../../web/root</module>
+    <module>../../web/flows</module>
+    <module>../../web/devices</module>
+    <module>../../web/troubleshoot</module>
+    <module>../../web/topology</module>
+    
+    <!-- Northbound bundles -->
+    <module>../../northbound/commons</module>
+    <module>../../northbound/topology</module>
+    <module>../../northbound/staticrouting</module>
+    <module>../../northbound/statistics</module>
+    <module>../../northbound/flowprogrammer</module>
+    <module>../../northbound/hosttracker</module>
+    <module>../../northbound/subnets</module>
+    <module>../../northbound/switchmanager</module>
 
     <!-- Debug and logging -->
     <module>../../logging/bridge</module>
+
+       <!-- Southbound bundles -->
+    <module>../../protocol_plugins/openflow</module>
+       
+    <!-- Samples -->
+    <module>../../samples/simpleforwarding</module>
   </modules>
   
   <build>
index 51152c9ace324df7ea063bb82ccbb59dfa3c5f51..b4afa61b61214a80c639e45eb11b66bcf891b251 100644 (file)
   <packaging>pom</packaging>
   <modules>
     <module>../../clustering/services</module>
+    <module>../../containermanager/api</module>
     <module>../../sal/api</module>
+
+    <!-- Northbound common hooks -->
+    <module>../../northbound/commons</module>
   </modules>
   
   <build>
diff --git a/opendaylight/forwarding/staticrouting/pom.xml b/opendaylight/forwarding/staticrouting/pom.xml
new file mode 100644 (file)
index 0000000..38f18bf
--- /dev/null
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>forwarding.staticrouting</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+       <artifactId>maven-bundle-plugin</artifactId>
+       <version>2.3.6</version>
+       <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Import-Package>
+                         org.opendaylight.controller.sal.utils,
+                         org.opendaylight.controller.sal.core,
+                         org.opendaylight.controller.configuration,
+              org.opendaylight.controller.forwardingrulesmanager,
+              org.opendaylight.controller.hosttracker,
+              org.opendaylight.controller.hosttracker.hostAware,
+              org.opendaylight.controller.clustering.services,
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.sal.routing,
+              org.opendaylight.controller.topologymanager,
+              org.eclipse.osgi.framework.console,
+              org.osgi.framework,
+              org.slf4j,
+              org.apache.felix.dm,
+              org.apache.commons.lang3.builder
+            </Import-Package>
+           <Export-Package>
+              org.opendaylight.controller.forwarding.staticrouting
+            </Export-Package>
+               <Bundle-Activator>
+                 org.opendaylight.controller.forwarding.staticrouting.internal.Activator
+               </Bundle-Activator>
+         </instructions>
+       </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>topologymanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>forwardingrulesmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>hosttracker</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>configuration</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>    
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+       <dependency>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>sal</artifactId>
+               <version>0.4.0-SNAPSHOT</version>
+       </dependency>    
+  </dependencies>
+</project>
diff --git a/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/IForwardingStaticRouting.java b/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/IForwardingStaticRouting.java
new file mode 100644 (file)
index 0000000..9b9c0b0
--- /dev/null
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting;
+
+import java.net.InetAddress;
+import java.util.concurrent.ConcurrentMap;
+/**
+ * 
+ * This interface provides APIs to configure and manage static routes.
+ *
+ */
+import org.opendaylight.controller.sal.utils.Status;
+
+public interface IForwardingStaticRouting {
+
+    /**
+     * Retrieves the StaticRoute that has the longest prefix matching the ipAddress.
+     * @param ipAddress (InetAddress) the IP address
+     * @return StaticRoute
+     */
+    public StaticRoute getBestMatchStaticRoute(InetAddress ipAddress);
+
+    /**
+     * Returns all the StaticRouteConfig
+     * @return all the StaticRouteConfig
+     */
+    public ConcurrentMap<String, StaticRouteConfig> getStaticRouteConfigs();
+
+    /**
+     * Adds a StaticRouteConfig
+     * @param config: the StaticRouteConfig to be added
+     * @return a text string indicating the result of the operation..
+     * If the operation is successful, the return string will be "SUCCESS"
+     */
+    public Status addStaticRoute(StaticRouteConfig config);
+
+    /**
+     * Removes  the named StaticRouteConfig
+     * @param name: the name of the StaticRouteConfig to be removed
+     * @return a text string indicating the result of the operation.
+     * If the operation is successful, the return string will be "SUCCESS"
+     */
+    public Status removeStaticRoute(String name);
+
+    /**
+     * Saves the config
+     * @return a text string indicating the result of the operation.
+     * If the operation is successful, the return string will be "Success"
+     */
+    Status saveConfig();
+}
diff --git a/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/IStaticRoutingAware.java b/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/IStaticRoutingAware.java
new file mode 100644 (file)
index 0000000..f650ee9
--- /dev/null
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting;
+
+/**
+ * Interface that will be implemented by the modules that want to
+ * know when a Static Route is added or deleted.
+ *
+ */
+public interface IStaticRoutingAware {
+
+    /**
+     * This method  is called when a StaticRoute has added or deleted.
+     * @param s: StaticRoute
+     * @param added: boolean true if the static route is added,
+     */
+    public void staticRouteUpdate(StaticRoute s, boolean added);
+}
diff --git a/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/StaticRoute.java b/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/StaticRoute.java
new file mode 100644 (file)
index 0000000..beec70e
--- /dev/null
@@ -0,0 +1,316 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.BitBufferHelper;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+/**
+ * This class defines a static route object.
+ */
+public class StaticRoute {
+    /**
+     * This Enum defines the possible types for the next hop address.
+     */
+    public enum NextHopType {
+        IPADDRESS("nexthop-ip"), SWITCHPORT("nexthop-interface");
+        private NextHopType(String name) {
+            this.name = name;
+        }
+
+        private String name;
+
+        public String toString() {
+            return name;
+        }
+
+        public static NextHopType fromString(String str) {
+            if (str == null)
+                return IPADDRESS;
+            if (str.equals(IPADDRESS.toString()))
+                return IPADDRESS;
+            if (str.equals(SWITCHPORT.toString()))
+                return SWITCHPORT;
+            return IPADDRESS;
+        }
+    }
+
+    InetAddress networkAddress;
+    InetAddress mask;
+    NextHopType type;
+    InetAddress nextHopAddress;
+    Node node;
+    NodeConnector port;
+    HostNodeConnector host;
+
+    /**
+     * Create a static route object with no specific information.
+     */
+    public StaticRoute() {
+
+    }
+
+    /**
+     * Create a static route object from the StaticRouteConfig.
+     * @param: config: StaticRouteConfig
+     */
+    public StaticRoute(StaticRouteConfig config) {
+        networkAddress = config.getStaticRouteIP();
+        mask = StaticRoute.getV4AddressMaskFromDecimal(config
+                .getStaticRouteMask());
+        type = NextHopType.fromString(config.getNextHopType());
+        nextHopAddress = config.getNextHopIP();
+        Map<Long, Short> switchPort = config.getNextHopSwitchPorts();
+        if ((switchPort != null) && (switchPort.size() == 1)) {
+            node = NodeCreator.createOFNode((Long) switchPort.keySet()
+                    .toArray()[0]);
+            port = NodeConnectorCreator.createOFNodeConnector(
+                    (Short) switchPort.values().toArray()[0], node);
+        }
+    }
+
+    /**
+     * Get the IP address portion of the sub-network of the static route.
+     * @return InetAddress: the IP address portion of the sub-network of the static route
+     */
+    public InetAddress getNetworkAddress() {
+        return networkAddress;
+    }
+
+    /**
+     * Set the IP address portion of the sub-network of the static route.
+     * @param networkAddress The IP address (InetAddress) to be set
+     */
+    public void setNetworkAddress(InetAddress networkAddress) {
+        this.networkAddress = networkAddress;
+    }
+
+    /**
+     * Get the mask of the sub-network of the static route.
+     * @return mask: the mask  (InetAddress) of the sub-network of the static route
+     */
+    public InetAddress getMask() {
+        return mask;
+    }
+
+    /**
+     * Set the sub-network's mask of the static route.
+     * @param mask The mask (InetAddress) to be set
+     */
+    public void setMask(InetAddress mask) {
+        this.mask = mask;
+    }
+
+    /**
+     * Get the NextHopeType of the static route.
+     * @return type: NextHopeType
+     */
+    public NextHopType getType() {
+        return type;
+    }
+
+    /**
+     * Set the nextHopType.
+     * @param type The NextHopType to be set
+     */
+    public void setType(NextHopType type) {
+        this.type = type;
+    }
+
+    /**
+     * Get the next hop IP address.
+     * @return: nextHopAddress (InetAddress)
+     */
+    public InetAddress getNextHopAddress() {
+        return nextHopAddress;
+    }
+
+    /**
+     * Set the next hop IP address.
+     * @param nextHopAddress The IP address (InetAddress) to be set
+     */
+    public void setNextHopAddress(InetAddress nextHopAddress) {
+        this.nextHopAddress = nextHopAddress;
+    }
+
+    /**
+     * Get the Node associated with the static route.
+     * @return: Node
+     */
+    public Node getNode() {
+        return node;
+    }
+
+    /**
+     * Set the node associated to the static route.
+     * @param node: The node to be set
+     */
+    public void setNode(Node node) {
+        this.node = node;
+    }
+
+    /**
+     * Set the port associated to the static route.
+     * @param port The port (NodeConnector) to be set
+     */
+    public void setPort(NodeConnector port) {
+        this.port = port;
+    }
+
+    /**
+     * Get the port associated to the static route.
+     * @return port: The port (NodeConnector)
+     */
+    public NodeConnector getPort() {
+        return port;
+    }
+
+    /**
+     * Get the Host associated to static route.
+     * @return host:  The host (HostNodeConnector)
+     */
+    public HostNodeConnector getHost() {
+        return host;
+    }
+
+    /**
+     * Set the host associated to the static route.
+     * @param host: (HostNodeConnector) to be set
+     */
+    public void setHost(HostNodeConnector host) {
+        this.host = host;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((host == null) ? 0 : host.hashCode());
+        result = prime * result + ((mask == null) ? 0 : mask.hashCode());
+        result = prime * result
+                + ((networkAddress == null) ? 0 : networkAddress.hashCode());
+        result = prime * result
+                + ((nextHopAddress == null) ? 0 : nextHopAddress.hashCode());
+        result = prime * result + ((port == null) ? 0 : port.hashCode());
+        result = prime * result + ((node == null) ? 0 : node.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "StaticRoute [networkAddress=" + networkAddress + ", mask="
+                + mask + ", type=" + type.toString() + ", nextHopAddress="
+                + nextHopAddress + ", swid=" + node + ", port=" + port
+                + ", host=" + host + "]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        StaticRoute other = (StaticRoute) obj;
+        if (!networkAddress.equals(other.networkAddress))
+            return false;
+        if (!mask.equals(other.mask))
+            return false;
+        return true;
+    }
+
+    private static InetAddress getV4AddressMaskFromDecimal(int mask) {
+        int netmask = 0;
+        for (int i = 0; i < mask; i++) {
+            netmask |= (1 << 31 - i);
+        }
+
+        try {
+            return InetAddress.getByAddress(BitBufferHelper
+                    .toByteArray(netmask));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private void applyV4MaskOnByteBuffer(ByteBuffer bb, ByteBuffer bbMask) {
+        for (int i = 0; i < bb.array().length; i++) {
+            bb.put(i, (byte) (bb.get(i) & bbMask.get(i)));
+        }
+    }
+
+    /**
+     * Compute and return the IP address  with longest prefix match from the static route based on the
+     *  destNetworkAddress. Currently it only take IPv4 address format (Inet4Address)
+     * @param destNetworkAddress: the IP address to be based on
+     * @return: InetAddress: the IPv4 address with the longest prefix matching the static route.
+     * If the destNetworkkAddress is not IPv4 format, it will return null.
+     */
+    public InetAddress longestPrefixMatch(InetAddress destNetworkAddress) {
+        if (destNetworkAddress instanceof Inet4Address) {
+            ByteBuffer bbdest = ByteBuffer
+                    .wrap(destNetworkAddress.getAddress());
+            ByteBuffer bbself = ByteBuffer.wrap(networkAddress.getAddress());
+
+            ByteBuffer bbMask = ByteBuffer.wrap(mask.getAddress());
+
+            applyV4MaskOnByteBuffer(bbdest, bbMask);
+            applyV4MaskOnByteBuffer(bbself, bbMask);
+
+            if (bbdest.equals(bbself)) {
+                try {
+                    return InetAddress.getByAddress(bbself.array());
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Compare the static route with another static route. It only handles(for now) IPv4Address.
+     * @param s: the other StaticRoute
+     * @return: 0 if they are the same
+     */
+    public int compareTo(StaticRoute s) {
+        if (s == null)
+            return 1;
+        if ((networkAddress instanceof Inet6Address)
+                || (s.getNetworkAddress() instanceof Inet6Address)) {
+            // HANDLE IPv6 Later
+            return 1;
+        }
+
+        ByteBuffer bbchallenger = ByteBuffer.wrap(s.getNetworkAddress()
+                .getAddress());
+        ByteBuffer bbself = ByteBuffer.wrap(networkAddress.getAddress());
+        ByteBuffer bbChallengerMask = ByteBuffer.wrap(s.getMask().getAddress());
+        ByteBuffer bbSelfMask = ByteBuffer.wrap(getMask().getAddress());
+
+        applyV4MaskOnByteBuffer(bbchallenger, bbChallengerMask);
+        applyV4MaskOnByteBuffer(bbself, bbSelfMask);
+        return bbself.compareTo(bbchallenger);
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteConfig.java b/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteConfig.java
new file mode 100644 (file)
index 0000000..706c29e
--- /dev/null
@@ -0,0 +1,345 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.opendaylight.controller.sal.utils.GUIField;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+
+/**
+ * This class defines all the necessary configuration information for a static route.
+ */
+public class StaticRouteConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final String regexSubnet = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])[/](\\d|[12]\\d|3[0-2])$";
+    private static final String regexIP = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
+    private static final String regexDatapathID = "^([0-9a-fA-F]{1,2}[:-]){7}[0-9a-fA-F]{1,2}$";
+    private static final String regexDatapathIDLong = "^[0-9a-fA-F]{1,16}$";
+    private static final String prettyFields[] = { GUIField.NAME.toString(),
+            GUIField.STATICROUTE.toString(), GUIField.NEXTHOP.toString() };
+    private transient String nextHopType; // Ignoring NextHopType for now. Supporting just the next-hop IP-Address feature for now.
+    // Order matters: JSP file expects following fields in the following order
+    private String name;
+    private String staticRoute; // A.B.C.D/MM  Where A.B.C.D is the Default Gateway IP (L3) or ARP Querier IP (L2)
+    private String nextHop; // NextHop IP-Address (or) datapath ID/port list: xx:xx:xx:xx:xx:xx:xx:xx/a,b,c-m,r-t,y
+
+    /**
+     * Create a static route configuration  with no specific information.
+     */
+    public StaticRouteConfig() {
+        super();
+        nextHopType = StaticRoute.NextHopType.IPADDRESS.toString();
+    }
+
+    /**
+     * Create a static route configuration with all the information.
+     * @param name The name (String) of the static route config
+     * @param staticRoute The string representation of the route. e.g. 192.168.1.1/24
+     * @param nextHop The string representation of the next hop IP address. e.g. 10.10.1.1
+     */
+    public StaticRouteConfig(String name, String staticRoute, String nextHop) {
+        super();
+        this.name = name;
+        this.staticRoute = staticRoute;
+        this.nextHop = nextHop;
+    }
+
+    /**
+     * Get the name of the StaticRouteConfig.
+     * @return: The name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the name of the StaticRouteConfig.
+     * @param name The name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Get the string representation of the static route.
+     * @return The string representation of the static route
+     */
+    public String getStaticRoute() {
+        return staticRoute;
+    }
+
+    /**
+     * Set the static route of the StaticRouteConfig.
+     * @param staticRoute The string representation of the static route
+     */
+    public void setStaticRoute(String staticRoute) {
+        this.staticRoute = staticRoute;
+    }
+
+    /**
+     * Get the string representation of the next hop address type.
+     * @return The string representation of the next hop address type
+     */
+    public String getNextHopType() {
+        if (nextHopType == null)
+            return StaticRoute.NextHopType.IPADDRESS.toString();
+        return nextHopType;
+    }
+
+    /**
+     * Set the next hop address type.
+     * @param nextHopType The string representation of the next hop address type
+     */
+    public void setNextHopType(String nextHopType) {
+        this.nextHopType = nextHopType;
+    }
+
+    /**
+     * Get all the supported next hop address types.
+     * @return The list of supported next hop address types
+     */
+    public static List<String> getSupportedNextHopTypes() {
+        List<String> s = new ArrayList<String>();
+        for (StaticRoute.NextHopType nh : StaticRoute.NextHopType.values()) {
+            s.add(nh.toString());
+        }
+        return s;
+    }
+
+    /**
+     * Get the next hop address
+     * @return The string represenation of the next hop address
+     */
+    public String getNextHop() {
+        return nextHop;
+    }
+
+    /**
+     * Set the next hop address.
+     * @param nextHop: The string representation of the next hop address to be set
+     */
+    public void setNextHop(String nextHop) {
+        this.nextHop = nextHop;
+    }
+
+    /**
+     * Return a string with text indicating if the config is valid.
+     * @return SUCCESS if the config is valid
+     */
+    public Status isValid() {
+        if ((name == null) || (name.trim().length() < 1)) {
+            return new Status(StatusCode.BADREQUEST,
+                       "Invalid Static Route name");
+        }
+        if (!isValidStaticRouteEntry()) {
+            return new Status(StatusCode.BADREQUEST,
+                       "Invalid Static Route entry. Please use the " +
+                       "IPAddress/mask format. Default gateway " +
+                       "(0.0.0.0/0) is NOT supported.");
+        }
+        if (!isValidNextHop()) {
+            return new Status(StatusCode.BADREQUEST,
+                       "Invalid NextHop IP Address configuration. " +
+                                       "Please use the X.X.X.X format.");
+        }
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private boolean isValidAddress(String address) {
+        if ((address != null) && address.matches(regexIP)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isValidStaticRouteEntry() {
+        if ((staticRoute != null) && staticRoute.matches(regexSubnet)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isValidNextHop() {
+        if (getNextHopType().equalsIgnoreCase(
+                StaticRoute.NextHopType.IPADDRESS.toString())) {
+            return isValidNextHopIP();
+        } else if (getNextHopType().equalsIgnoreCase(
+                StaticRoute.NextHopType.SWITCHPORT.toString())) {
+            return isValidSwitchId();
+        }
+        return false;
+    }
+
+    private boolean isValidNextHopIP() {
+        return isValidAddress(nextHop);
+    }
+
+    private boolean isValidSwitchId(String switchId) {
+        return (switchId != null && (switchId.matches(regexDatapathID) || switchId
+                .matches(regexDatapathIDLong)));
+    }
+
+    private boolean isValidSwitchId() {
+        if (getNextHopType().equalsIgnoreCase(
+                StaticRoute.NextHopType.SWITCHPORT.toString())) {
+            String pieces[] = nextHop.split("/");
+            if (pieces.length < 2)
+                return false;
+            return isValidSwitchId(pieces[0]);
+        }
+        return false;
+    }
+
+    /**
+     * Return the IP address of the static route.
+     * @return The IP address
+     */
+    public InetAddress getStaticRouteIP() {
+        if (!isValidStaticRouteEntry())
+            return null;
+        InetAddress ip = null;
+        try {
+            ip = InetAddress.getByName(staticRoute.split("/")[0]);
+        } catch (UnknownHostException e1) {
+            return null;
+        }
+        return ip;
+    }
+
+    /**
+     * Return the bit-mask length of the static route.
+     * @return The bit-mask length
+     */
+    public Short getStaticRouteMask() {
+        Short maskLen = 0;
+        if (isValidStaticRouteEntry()) {
+            String[] s = staticRoute.split("/");
+            maskLen = (s.length == 2) ? Short.valueOf(s[1]) : 32;
+        }
+        return maskLen;
+    }
+
+    /**
+     * Return the IP address of the next hop.
+     * @return the IP address
+     */
+    public InetAddress getNextHopIP() {
+        if ((getNextHopType()
+                .equalsIgnoreCase(StaticRoute.NextHopType.IPADDRESS.toString()))
+                && isValidNextHopIP()) {
+            InetAddress ip = null;
+            try {
+                ip = InetAddress.getByName(nextHop);
+            } catch (UnknownHostException e1) {
+                return null;
+            }
+            return ip;
+        }
+        return null;
+    }
+
+/**
+ * Return the switch ID and the port ID of the next hop address.
+ * @return The switchID (Long) and PortID (Short) in the map
+ */
+    public Map<Long, Short> getNextHopSwitchPorts() {
+        // codedSwitchPorts = xx:xx:xx:xx:xx:xx:xx:xx/port-number
+        if (getNextHopType().equalsIgnoreCase(
+                StaticRoute.NextHopType.SWITCHPORT.toString())) {
+            Map<Long, Short> sp = new HashMap<Long, Short>(1);
+            String pieces[] = nextHop.split("/");
+            sp.put(getSwitchIDLong(pieces[0]), Short.valueOf(pieces[1]));
+            return sp;
+        }
+        return null;
+    }
+
+    private long getSwitchIDLong(String switchId) {
+        int radix = 16;
+        String switchString = "0";
+
+        if (isValidSwitchId(switchId)) {
+            if (switchId.contains(":")) {
+                // Handle the 00:00:AA:BB:CC:DD:EE:FF notation
+                switchString = switchId.replace(":", "");
+            } else if (switchId.contains("-")) {
+                // Handle the 00-00-AA-BB-CC-DD-EE-FF notation
+                switchString = switchId.replace("-", "");
+            } else {
+                // Handle the 0123456789ABCDEF notation
+                switchString = switchId;
+            }
+        }
+        return Long.parseLong(switchString, radix);
+    }
+
+    /**
+     * Return all the field names of the config.
+     * @return The list containing all the field names
+     */
+    public static List<String> getFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (Field fld : StaticRouteConfig.class.getDeclaredFields()) {
+            fieldList.add(fld.getName());
+        }
+        //remove the 6 static fields + NextHopType
+        for (short i = 0; i < 7; i++) {
+            fieldList.remove(0);
+        }
+        return fieldList;
+    }
+
+    /**
+     * Return all the GUI field names of the config.
+     * @return The list containing all the GUI field names
+     */
+    public static List<String> getGuiFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (String str : prettyFields) {
+            fieldList.add(str);
+        }
+        return fieldList;
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public String toString() {
+        return "StaticRouteConfig [name=" + name + ", staticRoute="
+                + staticRoute + ", nextHopType=" + nextHopType + ", nextHop="
+                + nextHop + "]";
+    }
+}
diff --git a/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/internal/Activator.java b/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/internal/Activator.java
new file mode 100644 (file)
index 0000000..37f3873
--- /dev/null
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.internal;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.forwarding.staticrouting.IForwardingStaticRouting;
+import org.opendaylight.controller.forwarding.staticrouting.IStaticRoutingAware;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.IfNewHostNotify;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { StaticRoutingImplementation.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(StaticRoutingImplementation.class)) {
+            Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+            Set<String> propSet = new HashSet<String>();
+            propSet.add("forwarding.staticrouting.configSaveEvent");
+            props.put("cachenames", propSet);
+            // export the service
+
+            c.setInterface(new String[] { ICacheUpdateAware.class.getName(),
+                    IForwardingStaticRouting.class.getName(),
+                    IfNewHostNotify.class.getName(),
+                    IConfigurationContainerAware.class.getName() }, props);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IClusterContainerServices.class).setCallbacks(
+                    "setClusterContainerService",
+                    "unsetClusterContainerService").setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IfIptoHost.class).setCallbacks("setHostTracker",
+                    "unsetHostTracker").setRequired(true));
+
+            // Static routing aware there could be many
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IStaticRoutingAware.class).setCallbacks(
+                    "setStaticRoutingAware", "unsetStaticRoutingAware")
+                    .setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/internal/StaticRoutingImplementation.java b/opendaylight/forwarding/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/internal/StaticRoutingImplementation.java
new file mode 100644 (file)
index 0000000..fd043fd
--- /dev/null
@@ -0,0 +1,520 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.internal;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Future;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.forwarding.staticrouting.IForwardingStaticRouting;
+import org.opendaylight.controller.forwarding.staticrouting.IStaticRoutingAware;
+import org.opendaylight.controller.forwarding.staticrouting.StaticRoute;
+import org.opendaylight.controller.forwarding.staticrouting.StaticRouteConfig;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.IfNewHostNotify;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.IObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectWriter;
+import org.opendaylight.controller.sal.utils.Status;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Static Routing feature provides the bridge between SDN and Non-SDN networks.
+ *
+ *
+ *
+ */
+public class StaticRoutingImplementation implements IfNewHostNotify,
+        IForwardingStaticRouting, IObjectReader, IConfigurationContainerAware,
+        ICacheUpdateAware<Long, String> {
+    private static Logger log = LoggerFactory
+            .getLogger(StaticRoutingImplementation.class);
+    private static String ROOT = GlobalConstants.STARTUPHOME.toString();
+    private static final String SAVE = "Save";
+    ConcurrentMap<String, StaticRoute> staticRoutes;
+    ConcurrentMap<String, StaticRouteConfig> staticRouteConfigs;
+    private IfIptoHost hostTracker;
+    private Timer gatewayProbeTimer;
+    private String staticRoutesFileName = null;
+    private Map<Long, String> configSaveEvent;
+    private IClusterContainerServices clusterContainerService = null;
+    private Set<IStaticRoutingAware> staticRoutingAware = Collections
+            .synchronizedSet(new HashSet<IStaticRoutingAware>());
+
+    void setStaticRoutingAware(IStaticRoutingAware s) {
+        if (this.staticRoutingAware != null) {
+            this.staticRoutingAware.add(s);
+        }
+    }
+
+    void unsetStaticRoutingAware(IStaticRoutingAware s) {
+        if (this.staticRoutingAware != null) {
+            this.staticRoutingAware.remove(s);
+        }
+    }
+
+    public void setHostTracker(IfIptoHost hostTracker) {
+        log.debug("Setting HostTracker");
+        this.hostTracker = hostTracker;
+    }
+
+    public void unsetHostTracker(IfIptoHost hostTracker) {
+        if (this.hostTracker == hostTracker) {
+            this.hostTracker = null;
+        }
+    }
+
+    public ConcurrentMap<String, StaticRouteConfig> getStaticRouteConfigs() {
+        return staticRouteConfigs;
+    }
+
+    public void setStaticRouteConfigs(
+            ConcurrentMap<String, StaticRouteConfig> staticRouteConfigs) {
+        this.staticRouteConfigs = staticRouteConfigs;
+    }
+
+    @Override
+    public Object readObject(ObjectInputStream ois)
+            throws FileNotFoundException, IOException, ClassNotFoundException {
+        // Perform the class deserialization locally, from inside the package
+        // where the class is defined
+        return ois.readObject();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadConfiguration() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<String, StaticRouteConfig> confList = (ConcurrentMap<String, StaticRouteConfig>) objReader
+                .read(this, staticRoutesFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (StaticRouteConfig conf : confList.values()) {
+            addStaticRoute(conf);
+        }
+    }
+
+    @Override
+    public Status saveConfig() {
+        // Publish the save config event to the cluster nodes
+        configSaveEvent.put(new Date().getTime(), SAVE);
+        return saveConfigInternal();
+    }
+
+    public Status saveConfigInternal() {
+        Status status;
+        ObjectWriter objWriter = new ObjectWriter();
+
+        status = objWriter.write(
+                new ConcurrentHashMap<String, StaticRouteConfig>(
+                        staticRouteConfigs), staticRoutesFileName);
+
+        if (status.isSuccess()) {
+            return status;
+        } else {
+            return new Status(StatusCode.INTERNALERROR, "Save failed");
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+       private void allocateCaches() {
+        if (this.clusterContainerService == null) {
+            log
+                    .info("un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+
+        try {
+            clusterContainerService.createCache(
+                    "forwarding.staticrouting.routes", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache(
+                    "forwarding.staticrouting.configs", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache(
+                    "forwarding.staticrouting.configSaveEvent", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+        } catch (CacheExistException cee) {
+            log
+                    .error("\nCache already exists - destroy and recreate if needed");
+        } catch (CacheConfigException cce) {
+            log.error("\nCache configuration invalid - check cache mode");
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    private void retrieveCaches() {
+        if (this.clusterContainerService == null) {
+            log
+                    .info("un-initialized clusterContainerService, can't retrieve cache");
+            return;
+        }
+
+        staticRoutes = (ConcurrentMap<String, StaticRoute>) clusterContainerService
+                .getCache("forwarding.staticrouting.routes");
+        if (staticRoutes == null) {
+            log.error("\nFailed to get rulesDB handle");
+        }
+
+        staticRouteConfigs = (ConcurrentMap<String, StaticRouteConfig>) clusterContainerService
+                .getCache("forwarding.staticrouting.configs");
+        if (staticRouteConfigs == null) {
+            log.error("\nFailed to get rulesDB handle");
+        }
+        configSaveEvent = (ConcurrentMap<Long, String>) clusterContainerService
+                .getCache("forwarding.staticrouting.configSaveEvent");
+        if (configSaveEvent == null) {
+            log.error("\nFailed to get cache for configSaveEvent");
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+       private void destroyCaches() {
+        if (this.clusterContainerService == null) {
+            log
+                    .info("un-initialized clusterContainerService, can't destroy cache");
+            return;
+        }
+
+        clusterContainerService.destroyCache("forwarding.staticrouting.routes");
+        clusterContainerService
+                .destroyCache("forwarding.staticrouting.configs");
+        clusterContainerService
+                .destroyCache("forwarding.staticrouting.configSaveEvent");
+
+    }
+
+    @Override
+    public void entryCreated(Long key, String cacheName, boolean local) {
+    }
+
+    @Override
+    public void entryUpdated(Long key, String new_value, String cacheName,
+            boolean originLocal) {
+        saveConfigInternal();
+    }
+
+    @Override
+    public void entryDeleted(Long key, String cacheName, boolean originLocal) {
+    }
+
+    private void notifyStaticRouteUpdate(StaticRoute s, boolean update) {
+        if (this.staticRoutingAware != null) {
+            log.info("Invoking StaticRoutingAware listeners");
+            synchronized (this.staticRoutingAware) {
+                for (IStaticRoutingAware ra : this.staticRoutingAware) {
+                    try {
+                        ra.staticRouteUpdate(s, update);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    private class NotifyStaticRouteThread extends Thread {
+        private StaticRoute staticRoute;
+        private boolean added;
+
+        public NotifyStaticRouteThread(StaticRoute s, boolean update) {
+            this.staticRoute = s;
+            this.added = update;
+        }
+
+        public void run() {
+            if (!added
+                    || (staticRoute.getType() == StaticRoute.NextHopType.SWITCHPORT)) {
+                notifyStaticRouteUpdate(staticRoute, added);
+            } else {
+                HostNodeConnector host = hostTracker.hostQuery(staticRoute
+                        .getNextHopAddress());
+                if (host == null) {
+                    Future<HostNodeConnector> future = hostTracker
+                            .discoverHost(staticRoute.getNextHopAddress());
+                    if (future != null) {
+                        try {
+                            host = future.get();
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+                if (host != null) {
+                    staticRoute.setHost(host);
+                    notifyStaticRouteUpdate(staticRoute, added);
+                }
+            }
+        }
+    }
+
+    private void checkAndUpdateListeners(StaticRoute staticRoute, boolean added) {
+        new NotifyStaticRouteThread(staticRoute, added).start();
+    }
+
+    private void notifyHostUpdate(HostNodeConnector host, boolean added) {
+        if (host == null)
+            return;
+        for (StaticRoute s : staticRoutes.values()) {
+            if (s.getType() == StaticRoute.NextHopType.SWITCHPORT)
+                continue;
+            if (s.getNextHopAddress().equals(host.getNetworkAddress())) {
+                if (added) {
+                    s.setHost(host);
+                } else {
+                    s.setHost(null);
+                }
+                notifyStaticRouteUpdate(s, added);
+            }
+        }
+    }
+
+    @Override
+    public void notifyHTClient(HostNodeConnector host) {
+        notifyHostUpdate(host, true);
+    }
+
+    @Override
+    public void notifyHTClientHostRemoved(HostNodeConnector host) {
+        notifyHostUpdate(host, false);
+    }
+
+    public boolean isIPv4AddressValid(String cidr) {
+        if (cidr == null)
+            return false;
+
+        String values[] = cidr.split("/");
+        Pattern ipv4Pattern = Pattern
+                .compile("(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])");
+        Matcher mm = ipv4Pattern.matcher(values[0]);
+        if (!mm.matches()) {
+            log.debug("IPv4 source address {} is not valid", cidr);
+            return false;
+        }
+        if (values.length >= 2) {
+            int prefix = Integer.valueOf(values[1]);
+            if ((prefix < 0) || (prefix > 32)) {
+                log.debug("prefix {} is not valid", prefix);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static short getUnsignedByte(ByteBuffer bb, int position) {
+        return ((short) (bb.get(position) & (short) 0xff));
+    }
+
+    public static int compareByteBuffers(ByteBuffer buf1, ByteBuffer buf2) {
+        for (int i = 0; i < buf1.array().length; i++) {
+            if (getUnsignedByte(buf1, i) > getUnsignedByte(buf2, i)) {
+                return 1;
+            } else if (getUnsignedByte(buf1, i) < getUnsignedByte(buf2, i)) {
+                return -1;
+            }
+        }
+
+        return 0;
+    }
+
+    public StaticRoute getBestMatchStaticRoute(InetAddress ipAddress) {
+        ByteBuffer bblongestPrefix = null;
+        try {
+            bblongestPrefix = ByteBuffer.wrap(InetAddress.getByName("0.0.0.0")
+                    .getAddress());
+        } catch (Exception e) {
+            return null;
+        }
+
+        if (staticRoutes == null) {
+            return null;
+        }
+
+        StaticRoute longestPrefixRoute = null;
+        for (StaticRoute s : staticRoutes.values()) {
+            InetAddress prefix = s.longestPrefixMatch(ipAddress);
+            if ((prefix != null) && (prefix instanceof Inet4Address)) {
+                ByteBuffer bbtmp = ByteBuffer.wrap(prefix.getAddress());
+                if (compareByteBuffers(bbtmp, bblongestPrefix) > 0) {
+                    bblongestPrefix = bbtmp;
+                    longestPrefixRoute = s;
+                }
+            }
+        }
+        return longestPrefixRoute;
+    }
+
+    public Status addStaticRoute(StaticRouteConfig config) {
+        Status status;
+
+        status = config.isValid();
+        if (!status.isSuccess()) {
+            return status;
+        }
+        if (staticRouteConfigs.get(config.getName()) != null) {
+               return new Status(StatusCode.CONFLICT,
+                               "A valid Static Route configuration with this name " +
+                                               "already exists. Please use a different name");
+        }
+        for (StaticRouteConfig s : staticRouteConfigs.values()) {
+            if (s.equals(config)) {
+               return new Status(StatusCode.CONFLICT,
+                               "This conflicts with an existing Static Route " +
+                                       "Configuration. Please check the configuration " +
+                                               "and try again");
+            }
+        }
+
+        staticRouteConfigs.put(config.getName(), config);
+        StaticRoute sRoute = new StaticRoute(config);
+        staticRoutes.put(config.getName(), sRoute);
+        checkAndUpdateListeners(sRoute, true);
+        return status; 
+    }
+
+    public Status removeStaticRoute(String name) {
+        staticRouteConfigs.remove(name);
+        StaticRoute sRoute = staticRoutes.remove(name);
+        if (sRoute != null) {
+            checkAndUpdateListeners(sRoute, false);
+            return new Status(StatusCode.SUCCESS, null);
+        }
+        return new Status(StatusCode.NOTFOUND, 
+                       "Static Route with name " + name + " is not found");
+    }
+
+    void setClusterContainerService(IClusterContainerServices s) {
+        log.debug("Cluster Service set");
+        this.clusterContainerService = s;
+    }
+
+    void unsetClusterContainerService(IClusterContainerServices s) {
+        if (this.clusterContainerService == s) {
+            log.debug("Cluster Service removed!");
+            this.clusterContainerService = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init(Component c) {
+        String containerName = null;
+        Dictionary props = c.getServiceProperties();
+        if (props != null) {
+            containerName = (String) props.get("containerName");
+        } else {
+            // In the Global instance case the containerName is empty
+            containerName = "";
+        }
+
+        staticRoutesFileName = ROOT + "staticRouting_" + containerName
+                + ".conf";
+
+        log.debug("forwarding.staticrouting starting on container "
+                + containerName);
+        //staticRoutes = new ConcurrentHashMap<String, StaticRoute>();
+        allocateCaches();
+        retrieveCaches();
+
+        if (staticRouteConfigs.isEmpty())
+            loadConfiguration();
+
+        /*
+         *  Slow probe to identify any gateway that might have silently appeared
+         *  after the Static Routing Configuration.
+         */
+        gatewayProbeTimer = new Timer();
+        gatewayProbeTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                for (StaticRoute s : staticRoutes.values()) {
+                    if ((s.getType() == StaticRoute.NextHopType.IPADDRESS)
+                            && s.getHost() == null) {
+                        checkAndUpdateListeners(s, true);
+                    }
+                }
+            }
+        }, 60 * 1000, 60 * 1000);
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        log.debug("Destroy all the Static Routing Rules given we are "
+                + "shutting down");
+
+        destroyCaches();
+        gatewayProbeTimer.cancel();
+
+        // Clear the listener so to be ready in next life
+        this.staticRoutingAware.clear();
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    @Override
+    public Status saveConfiguration() {
+        return this.saveConfig();
+    }
+}
diff --git a/opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteConfigTest.java b/opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteConfigTest.java
new file mode 100644 (file)
index 0000000..84ec54b
--- /dev/null
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting;
+
+
+import java.net.InetAddress;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.forwarding.staticrouting.StaticRoute.NextHopType;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+
+public class StaticRouteConfigTest {
+       
+       @Test
+       public void testStaticRouteSetGet() {
+               StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig();
+               staticRouteConfig1.setName("route");
+               staticRouteConfig1.setStaticRoute("10.1.1.2/32");
+               staticRouteConfig1.setNextHop("200.2.2.2");
+               staticRouteConfig1.setNextHopType(NextHopType.IPADDRESS.toString());
+               StaticRouteConfig staticRouteConfig2 = new StaticRouteConfig("route", "10.1.1.2/32", "200.2.2.2");
+               
+               Assert.assertEquals(staticRouteConfig2.getName(), staticRouteConfig1.getName());
+               Assert.assertEquals(staticRouteConfig2.getStaticRoute(), staticRouteConfig1.getStaticRoute());
+               Assert.assertEquals(staticRouteConfig2.getNextHop(), staticRouteConfig1.getNextHop());
+               Assert.assertEquals("nexthop-ip", staticRouteConfig1.getNextHopType());
+       }
+               
+       @Test
+       public void testStaticRouteisValid() {  
+        StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig("route1", "10.1.1.254/24", "100.1.1.1");
+               Status receivedResponse1 = staticRouteConfig1.isValid();
+               Status expectedResponse1 = new Status(StatusCode.SUCCESS, null);
+               Assert.assertEquals(expectedResponse1.toString(), receivedResponse1.toString());
+               
+        StaticRouteConfig staticRouteConfig2 = new StaticRouteConfig("", "", "100.1.1.1");
+               Status receivedResponse2 = staticRouteConfig2.isValid();
+               Status expectedResponse2 = new Status(StatusCode.BADREQUEST,
+                       "Invalid Static Route name");
+               Assert.assertEquals(expectedResponse2.toString(), receivedResponse2.toString());
+
+        StaticRouteConfig staticRouteConfig3 = new StaticRouteConfig("route1", "10.1.1.254", "100.1.1.1");
+               Status receivedResponse3 = staticRouteConfig3.isValid();
+               Status expectedResponse3 = new Status(StatusCode.BADREQUEST,
+                       "Invalid Static Route entry. Please use the " +
+                       "IPAddress/mask format. Default gateway " +
+                       "(0.0.0.0/0) is NOT supported.");
+               Assert.assertEquals(expectedResponse3.toString(), receivedResponse3.toString());
+
+        StaticRouteConfig staticRouteConfig4 = new StaticRouteConfig("route1", "289.1.1.254/24", "100.1.1.1");
+               Status receivedResponse4 = staticRouteConfig4.isValid();
+               Status expectedResponse4 = new Status(StatusCode.BADREQUEST,
+                       "Invalid Static Route entry. Please use the " +
+                       "IPAddress/mask format. Default gateway " +
+                       "(0.0.0.0/0) is NOT supported.");
+               Assert.assertEquals(expectedResponse4.toString(), receivedResponse4.toString());
+               
+        StaticRouteConfig staticRouteConfig5 = new StaticRouteConfig("route1", "10.1.1.254/24", "100.1.1");
+               Status receivedResponse5 = staticRouteConfig5.isValid();
+               Status expectedResponse5 = new Status(StatusCode.BADREQUEST,
+                       "Invalid NextHop IP Address configuration. " +
+                               "Please use the X.X.X.X format.");
+               Assert.assertEquals(expectedResponse5.toString(), receivedResponse5.toString());
+       }
+       
+       @Test
+       public void testGetStaticRouteIP() {
+        StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig("route1", "10.1.1.0/24", "100.1.1.1");
+        InetAddress ip1 = staticRouteConfig1.getStaticRouteIP();
+               Assert.assertEquals("10.1.1.0", ip1.getHostAddress());        
+               
+        StaticRouteConfig staticRouteConfig2 = new StaticRouteConfig("route1", "10.1.1.0/80", "100.1.1.1");
+        InetAddress ip2 = staticRouteConfig2.getStaticRouteIP();
+               Assert.assertEquals(null, ip2);        
+
+       }
+       
+       @Test
+       public void testGetStaticRouteMask() {
+        StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig("route1", "10.1.1.0/24", "100.1.1.1");
+               Short receivedMaskLen1 = staticRouteConfig1.getStaticRouteMask();
+               Short expectedMaskLen1 = 24;
+               Assert.assertEquals(expectedMaskLen1, receivedMaskLen1);
+
+               StaticRouteConfig staticRouteConfig2 = new StaticRouteConfig("route1", "10.1.1.0/40", "100.1.1.1");
+               Short receivedMaskLen2 = staticRouteConfig2.getStaticRouteMask();
+               Short expectedMaskLen2 = 0;
+               Assert.assertEquals(expectedMaskLen2, receivedMaskLen2);
+       }
+       
+       @Test 
+       public void testGetNextHopIP() {
+        StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig("route1", "10.1.1.254/24", "100.1.1.1");
+        InetAddress ip1 = staticRouteConfig1.getNextHopIP();
+               Assert.assertEquals("100.1.1.1", ip1.getHostAddress());                
+
+               StaticRouteConfig staticRouteConfig2 = new StaticRouteConfig("route1", "10.1.1.254/24", "100.1.1");
+        InetAddress ip2 = staticRouteConfig2.getNextHopIP();
+               Assert.assertEquals(null, ip2);                
+       }
+       
+}
+
diff --git a/opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteTest.java b/opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/StaticRouteTest.java
new file mode 100644 (file)
index 0000000..f05b10c
--- /dev/null
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.forwarding.staticrouting.StaticRoute.NextHopType;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+public class StaticRouteTest {
+       
+       @Test
+       public void testStaticRouteGetSet() {
+               StaticRoute staticRoute = new StaticRoute();
+               InetAddress networkAddress = null;
+               InetAddress mask = null;
+               InetAddress nextHopAddress = null;
+               try {
+                       networkAddress = InetAddress.getByName("10.1.1.0");
+                       mask = InetAddress.getByName("255.255.255.0");
+                       nextHopAddress = InetAddress.getByName("200.0.0.1");
+                       
+               } catch (UnknownHostException e) {
+                       Assert.assertTrue(false);
+               }
+               staticRoute.setNetworkAddress(networkAddress);
+               Assert.assertEquals(networkAddress.getHostAddress(), staticRoute.getNetworkAddress().getHostAddress());
+               staticRoute.setMask(mask);
+               Assert.assertEquals(mask.getHostAddress(), staticRoute.getMask().getHostAddress());
+               staticRoute.setType(NextHopType.IPADDRESS);
+               Assert.assertEquals("nexthop-ip", staticRoute.getType().toString());
+               staticRoute.setNextHopAddress(nextHopAddress);
+               Assert.assertEquals(nextHopAddress.getHostAddress(), staticRoute.getNextHopAddress().getHostAddress());
+               Node node = NodeCreator.createOFNode(((long)10));
+               staticRoute.setNode(node);
+               Assert.assertEquals(node, staticRoute.getNode());
+               NodeConnector nc0 = NodeConnectorCreator.createOFNodeConnector((short)20, node);
+               staticRoute.setPort(nc0);
+               Assert.assertEquals(nc0, staticRoute.getPort());
+        InetAddress ip1 = null;
+        HostNodeConnector h1 = null;
+        try {
+            ip1 = InetAddress.getByName("192.1.1.1");
+        } catch (UnknownHostException e) {
+            Assert.assertTrue(false);
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            Assert.assertTrue(false);
+        }
+        staticRoute.setHost(h1);
+        Assert.assertEquals(h1, staticRoute.getHost());
+       }
+       
+       @Test
+       public void testStaticRouteComparison() {
+        StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig("route1", "10.1.1.0/24", "100.1.1.1");
+        StaticRouteConfig staticRouteConfig2 = new StaticRouteConfig("route2", "10.1.1.0/24", "100.2.1.1");
+        StaticRouteConfig staticRouteConfig3 = new StaticRouteConfig("route3", "10.2.1.0/24", "100.3.1.1");
+        StaticRouteConfig staticRouteConfig4 = new StaticRouteConfig("route4", "10.1.1.0/31", "");
+        StaticRoute staticRoute1 = new StaticRoute(staticRouteConfig1);
+        StaticRoute staticRoute2 = new StaticRoute(staticRouteConfig2);
+        StaticRoute staticRoute3 = new StaticRoute(staticRouteConfig3);
+        StaticRoute staticRoute4 = new StaticRoute(staticRouteConfig4);
+
+        Assert.assertTrue(staticRoute1.equals(staticRoute2));
+        Assert.assertFalse(staticRoute1.equals(staticRoute3));
+        Assert.assertFalse(staticRoute1.equals(staticRoute4));
+        
+        Assert.assertTrue(staticRoute1.compareTo(staticRoute2) == 0 ? true : false);
+        Assert.assertFalse(staticRoute1.compareTo(staticRoute3) == 0 ? true : false);
+        Assert.assertTrue(staticRoute1.compareTo(staticRoute4) == 0 ? true : false);
+                       
+       }
+       
+       @Test
+       public void testLongestPrefixMatch() {
+        StaticRouteConfig staticRouteConfig1 = new StaticRouteConfig("route1", "10.1.1.254/24", "100.1.1.1");
+        StaticRoute staticRoute1 = new StaticRoute(staticRouteConfig1);
+               InetAddress ip1 = null;
+               InetAddress ip2 = null;
+               try {
+                       ip1 = InetAddress.getByName("10.1.0.2");
+                       ip2 = InetAddress.getByName("10.1.1.2");
+               } catch (UnknownHostException e) {
+                       Assert.assertTrue(false);
+               }
+        InetAddress rxdIp1 = staticRoute1.longestPrefixMatch(ip1);
+        InetAddress rxdIp2 = staticRoute1.longestPrefixMatch(ip2);
+               Assert.assertEquals(null, rxdIp1);
+               Assert.assertEquals("10.1.1.0", rxdIp2.getHostAddress());
+       }
+}
diff --git a/opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/internal/StaticRoutingImplementationTest.java b/opendaylight/forwarding/staticrouting/src/test/java/org/opendaylight/controller/forwarding/staticrouting/internal/StaticRoutingImplementationTest.java
new file mode 100644 (file)
index 0000000..4df0590
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.internal;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class StaticRoutingImplementationTest {
+       
+       @Test
+       public void isIPv4AddressValidTest() {
+               StaticRoutingImplementation staticRouteImpl = new StaticRoutingImplementation();
+        Assert.assertTrue(staticRouteImpl.isIPv4AddressValid("192.168.100.0/24"));             
+        Assert.assertFalse(staticRouteImpl.isIPv4AddressValid("192.168.100.0/36"));            
+        Assert.assertFalse(staticRouteImpl.isIPv4AddressValid("192.168.300.0/32"));            
+       }
+}
+
+
+
+
diff --git a/opendaylight/forwardingrulesmanager/pom.xml b/opendaylight/forwardingrulesmanager/pom.xml
new file mode 100644 (file)
index 0000000..27c3b1e
--- /dev/null
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>forwardingrulesmanager</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+       <artifactId>maven-bundle-plugin</artifactId>
+       <version>2.3.6</version>
+       <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Include-Resource>
+           </Include-Resource>
+           <Export-Package>
+             org.opendaylight.controller.forwardingrulesmanager
+           </Export-Package>
+           <Import-Package>
+                     org.opendaylight.controller.clustering.services,
+                     org.opendaylight.controller.configuration,
+              org.opendaylight.controller.hosttracker,
+              org.opendaylight.controller.hosttracker.hostAware,
+              org.opendaylight.controller.switchmanager,
+              org.opendaylight.controller.sal.action,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.flowprogrammer,
+              org.opendaylight.controller.sal.match,
+                         org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.sal.packet,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              org.apache.felix.dm,
+              org.apache.commons.lang3.builder,
+              org.osgi.service.component,
+              org.slf4j,
+              org.eclipse.osgi.framework.console,
+                         org.osgi.framework
+           </Import-Package>
+        <Bundle-Activator>
+             org.opendaylight.controller.forwardingrulesmanager.internal.Activator
+               </Bundle-Activator>
+            <Require-Bundle>
+              org.opendaylight.controller.hosttracker
+            </Require-Bundle>
+            <Service-Component>
+            </Service-Component>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>hosttracker</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>configuration</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>    
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>sal</artifactId>
+               <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowConfig.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowConfig.java
new file mode 100644 (file)
index 0000000..f1217b5
--- /dev/null
@@ -0,0 +1,1431 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.ActionType;
+import org.opendaylight.controller.sal.action.Controller;
+import org.opendaylight.controller.sal.action.Drop;
+import org.opendaylight.controller.sal.action.Flood;
+import org.opendaylight.controller.sal.action.HwPath;
+import org.opendaylight.controller.sal.action.Loopback;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.PopVlan;
+import org.opendaylight.controller.sal.action.SetDlDst;
+import org.opendaylight.controller.sal.action.SetDlSrc;
+import org.opendaylight.controller.sal.action.SetNwDst;
+import org.opendaylight.controller.sal.action.SetNwSrc;
+import org.opendaylight.controller.sal.action.SetNwTos;
+import org.opendaylight.controller.sal.action.SetTpDst;
+import org.opendaylight.controller.sal.action.SetTpSrc;
+import org.opendaylight.controller.sal.action.SetVlanId;
+import org.opendaylight.controller.sal.action.SetVlanPcp;
+import org.opendaylight.controller.sal.action.SwPath;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainer;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.IPProtocols;
+import org.opendaylight.controller.sal.utils.NetUtils;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Configuration Java Object which represents a flow configuration information
+ * for Forwarding Rrules Manager.
+ */
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class FlowConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final Logger log = LoggerFactory.getLogger(FlowConfig.class);
+    private static final String staticFlowsGroup = "**StaticFlows";
+    private boolean dynamic;
+    private String status;
+
+    /*
+     * The order of the object data defined below is used directly in the UI built using JSP.
+     * Hence try to keep the order in a more logical way.
+     */
+    @XmlElement
+    private String installInHw;
+    @XmlElement
+    private String name;
+    @XmlElement
+    private Node node;
+    @XmlElement
+    private String ingressPort;
+    private String portGroup;
+    @XmlElement
+    private String priority;
+    @XmlElement
+    private String etherType;
+    @XmlElement
+    private String vlanId;
+    @XmlElement
+    private String vlanPriority;
+    @XmlElement
+    private String dlSrc;
+    @XmlElement
+    private String dlDst;
+    @XmlElement
+    private String nwSrc;
+    @XmlElement
+    private String nwDst;
+    @XmlElement
+    private String protocol;
+    @XmlElement
+    private String tosBits;
+    @XmlElement
+    private String tpSrc;
+    @XmlElement
+    private String tpDst;
+    @XmlElement
+    private String cookie;
+    @XmlElement
+    private String idleTimeout;
+    @XmlElement
+    private String hardTimeout;
+    @XmlElement
+    private List<String> actions;
+
+    private enum EtherIPType {
+        ANY, V4, V6;
+    };
+
+    private enum SetNextHopType {
+        CISCO_EXTENSION("Cisco NextHop Extension"), RESOLVE_L2RW(
+                "Resolve L2 Rewrite");
+
+        private SetNextHopType(String name) {
+            this.name = name;
+        }
+
+        private String name;
+
+        public String toString() {
+            return name;
+        }
+
+        public boolean equals(String type) {
+            if (type.trim().equalsIgnoreCase(name))
+                return true;
+            return false;
+        }
+    }
+
+    public FlowConfig() {
+    }
+
+    public FlowConfig(String installInHw, String name, Node node,
+            String priority, String cookie, String ingressPort,
+            String portGroup, String vlanId, String vlanPriority,
+            String etherType, String srcMac, String dstMac, String protocol,
+            String tosBits, String srcIP, String dstIP, String tpSrc,
+            String tpDst, String idleTimeout, String hardTimeout,
+            List<String> actions) {
+        super();
+        this.installInHw = installInHw;
+        this.name = name;
+        this.node = node;
+        this.priority = priority;
+        this.cookie = cookie;
+        this.ingressPort = ingressPort;
+        this.portGroup = portGroup;
+        this.vlanId = vlanId;
+        this.vlanPriority = vlanPriority;
+        this.etherType = etherType;
+        this.dlSrc = srcMac;
+        this.dlDst = dstMac;
+        this.protocol = protocol;
+        this.tosBits = tosBits;
+        this.nwSrc = srcIP;
+        this.nwDst = dstIP;
+        this.tpSrc = tpSrc;
+        this.tpDst = tpDst;
+        this.idleTimeout = idleTimeout;
+        this.hardTimeout = hardTimeout;
+        this.actions = actions;
+        this.status = StatusCode.SUCCESS.toString();
+    }
+
+    public FlowConfig(FlowConfig from) {
+        this.installInHw = from.installInHw;
+        this.name = from.name;
+        this.node = from.node;
+        this.priority = from.priority;
+        this.cookie = from.cookie;
+        this.ingressPort = from.ingressPort;
+        this.portGroup = from.portGroup;
+        this.vlanId = from.vlanId;
+        this.vlanPriority = from.vlanPriority;
+        this.etherType = from.etherType;
+        this.dlSrc = from.dlSrc;
+        this.dlDst = from.dlDst;
+        this.protocol = from.protocol;
+        this.tosBits = from.tosBits;
+        this.nwSrc = from.nwSrc;
+        this.nwDst = from.nwDst;
+        this.tpSrc = from.tpSrc;
+        this.tpDst = from.tpDst;
+        this.idleTimeout = from.idleTimeout;
+        this.hardTimeout = from.hardTimeout;
+        this.actions = new ArrayList<String>(from.actions);
+    }
+
+    public boolean installInHw() {
+        if (installInHw == null) {
+            // backward compatibility
+            installInHw = "true";
+        }
+        return installInHw.equals("true");
+    }
+
+    public void setInstallInHw(boolean inHw) {
+        installInHw = inHw ? "true" : "false";
+    }
+    
+    public String getInstallInHw() {
+       return installInHw;
+    }
+
+    public boolean isInternalFlow() {
+        // Controller generated static flows have name starting with "**"
+        if (this.name == null)
+            return false;
+        return this.name.startsWith("**");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        if (name == null) {
+            return;
+        }
+        this.name = name;
+    }
+
+    public Node getNode() {
+        return this.node;
+    }
+
+    public void setNode(Node node) {
+        this.node = node;
+    }
+
+    public String getPriority() {
+        return priority;
+    }
+
+    public void setPriority(String priority) {
+        this.priority = priority;
+    }
+
+    public String getCookie() {
+        return cookie;
+    }
+
+    public void setCookie(String cookie) {
+        this.cookie = cookie;
+    }
+
+    public String getIngressPort() {
+        return ingressPort;
+    }
+
+    public void setIngressPort(String ingressPort) {
+        this.ingressPort = ingressPort;
+    }
+
+    public String getPortGroup() {
+        return portGroup;
+    }
+
+    @Override
+    public String toString() {
+        return "FlowConfig [dynamic=" + dynamic + ", status=" + status
+                + ", installInHw=" + installInHw + ", name=" + name
+                + ", switchId=" + node + ", ingressPort=" + ingressPort
+                + ", portGroup=" + portGroup + ", etherType=" + etherType
+                + ", priority=" + priority + ", vlanId=" + vlanId
+                + ", vlanPriority=" + vlanPriority + ", dlSrc=" + dlSrc
+                + ", dlDst=" + dlDst + ", nwSrc=" + nwSrc + ", nwDst=" + nwDst
+                + ", protocol=" + protocol + ", tosBits=" + tosBits
+                + ", tpSrc=" + tpSrc + ", tpDst=" + tpDst + ", cookie="
+                + cookie + ", idleTimeout=" + idleTimeout + ", hardTimeout="
+                + hardTimeout + ", actions=" + actions + "]";
+    }
+
+    public void setPortGroup(String portGroup) {
+        this.portGroup = portGroup;
+    }
+
+    public String getVlanId() {
+        return vlanId;
+    }
+
+    public void setVlanId(String vlanId) {
+        this.vlanId = vlanId;
+    }
+
+    public String getVlanPriority() {
+        return vlanPriority;
+    }
+
+    public void setVlanPriority(String vlanPriority) {
+        this.vlanPriority = vlanPriority;
+    }
+
+    public String getEtherType() {
+        return etherType;
+    }
+
+    public void setEtherType(String etherType) {
+        this.etherType = etherType;
+    }
+
+    public String getSrcMac() {
+        return dlSrc;
+    }
+
+    public void setSrcMac(String srcMac) {
+        this.dlSrc = srcMac;
+    }
+
+    public String getDstMac() {
+        return dlDst;
+    }
+
+    public void setDstMac(String dstMac) {
+        this.dlDst = dstMac;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public String getTosBits() {
+        return tosBits;
+    }
+
+    public void setTosBits(String tos_bits) {
+        this.tosBits = tos_bits;
+    }
+
+    public String getSrcIp() {
+        return nwSrc;
+    }
+
+    public void setSrcIp(String src_ip) {
+        this.nwSrc = src_ip;
+    }
+
+    public String getDstIp() {
+        return nwDst;
+    }
+
+    public void setDstIp(String dst_ip) {
+        this.nwDst = dst_ip;
+    }
+
+    public String getSrcPort() {
+        return tpSrc;
+    }
+
+    public void setSrcPort(String src_port) {
+        this.tpSrc = src_port;
+    }
+
+    public String getDstPort() {
+        return tpDst;
+    }
+
+    public void setDstPort(String dst_port) {
+        this.tpDst = dst_port;
+    }
+
+    public String getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    public void setIdleTimeout(String idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public String getHardTimeout() {
+        return hardTimeout;
+    }
+
+    public void setHardTimeout(String hardTimeout) {
+        this.hardTimeout = hardTimeout;
+    }
+
+    public boolean isIPv6() {
+        if (NetUtils.isIPv6AddressValid(this.getSrcIp())
+                || NetUtils.isIPv6AddressValid(this.getDstIp())) {
+            return true;
+        }
+        return false;
+    }
+
+    public List<String> getActions() {
+        return actions;
+    }
+
+    public void setActions(List<String> actions) {
+        this.actions = actions;
+    }
+
+    public static List<String> getFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (Field fld : FlowConfig.class.getDeclaredFields()) {
+            fieldList.add(fld.getName());
+        }
+        // Remove 4 static fields + 2 internal field
+        for (int i = 0; i < 6; i++)
+            fieldList.remove(0);
+
+        return fieldList;
+    }
+
+    public boolean isPortGroupEnabled() {
+        return (portGroup != null);
+    }
+
+    public boolean isDynamic() {
+        return dynamic;
+    }
+
+    public void setDynamic(boolean dynamic) {
+        this.dynamic = dynamic;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public boolean isStatusSuccessful() {
+        return status.equals(StatusCode.SUCCESS.toString());
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((actions == null) ? 0 : actions.hashCode());
+        result = prime * result + ((cookie == null) ? 0 : cookie.hashCode());
+        result = prime * result + ((dlDst == null) ? 0 : dlDst.hashCode());
+        result = prime * result + ((dlSrc == null) ? 0 : dlSrc.hashCode());
+        result = prime * result + (dynamic ? 1231 : 1237);
+        result = prime * result
+                + ((etherType == null) ? 0 : etherType.hashCode());
+        result = prime * result
+                + ((ingressPort == null) ? 0 : ingressPort.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((nwDst == null) ? 0 : nwDst.hashCode());
+        result = prime * result + ((nwSrc == null) ? 0 : nwSrc.hashCode());
+        result = prime * result
+                + ((portGroup == null) ? 0 : portGroup.hashCode());
+        result = prime * result
+                + ((priority == null) ? 0 : priority.hashCode());
+        result = prime * result
+                + ((protocol == null) ? 0 : protocol.hashCode());
+        result = prime * result + ((node == null) ? 0 : node.hashCode());
+        result = prime * result + ((tosBits == null) ? 0 : tosBits.hashCode());
+        result = prime * result + ((tpDst == null) ? 0 : tpDst.hashCode());
+        result = prime * result + ((tpSrc == null) ? 0 : tpSrc.hashCode());
+        result = prime * result + ((vlanId == null) ? 0 : vlanId.hashCode());
+        result = prime * result
+                + ((vlanPriority == null) ? 0 : vlanPriority.hashCode());
+        result = prime * result
+                + ((idleTimeout == null) ? 0 : idleTimeout.hashCode());
+        result = prime * result
+                + ((hardTimeout == null) ? 0 : hardTimeout.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        FlowConfig other = (FlowConfig) obj;
+        if (actions == null) {
+            if (other.actions != null)
+                return false;
+        } else if (!actions.equals(other.actions))
+            return false;
+        if (cookie == null) {
+            if (other.cookie != null)
+                return false;
+        } else if (!cookie.equals(other.cookie))
+            return false;
+        if (dlDst == null) {
+            if (other.dlDst != null)
+                return false;
+        } else if (!dlDst.equals(other.dlDst))
+            return false;
+        if (dlSrc == null) {
+            if (other.dlSrc != null)
+                return false;
+        } else if (!dlSrc.equals(other.dlSrc))
+            return false;
+        if (dynamic != other.dynamic)
+            return false;
+        if (etherType == null) {
+            if (other.etherType != null)
+                return false;
+        } else if (!etherType.equals(other.etherType))
+            return false;
+        if (ingressPort == null) {
+            if (other.ingressPort != null)
+                return false;
+        } else if (!ingressPort.equals(other.ingressPort))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (nwDst == null) {
+            if (other.nwDst != null)
+                return false;
+        } else if (!nwDst.equals(other.nwDst))
+            return false;
+        if (nwSrc == null) {
+            if (other.nwSrc != null)
+                return false;
+        } else if (!nwSrc.equals(other.nwSrc))
+            return false;
+        if (portGroup == null) {
+            if (other.portGroup != null)
+                return false;
+        } else if (!portGroup.equals(other.portGroup))
+            return false;
+        if (priority == null) {
+            if (other.priority != null)
+                return false;
+        } else if (!priority.equals(other.priority))
+            return false;
+        if (protocol == null) {
+            if (other.protocol != null)
+                return false;
+        } else if (!protocol.equals(other.protocol))
+            return false;
+        if (node == null) {
+            if (other.node != null)
+                return false;
+        } else if (!node.equals(other.node))
+            return false;
+        if (tosBits == null) {
+            if (other.tosBits != null)
+                return false;
+        } else if (!tosBits.equals(other.tosBits))
+            return false;
+        if (tpDst == null) {
+            if (other.tpDst != null)
+                return false;
+        } else if (!tpDst.equals(other.tpDst))
+            return false;
+        if (tpSrc == null) {
+            if (other.tpSrc != null)
+                return false;
+        } else if (!tpSrc.equals(other.tpSrc))
+            return false;
+        if (vlanId == null) {
+            if (other.vlanId != null)
+                return false;
+        } else if (!vlanId.equals(other.vlanId))
+            return false;
+        if (vlanPriority == null) {
+            if (other.vlanPriority != null)
+                return false;
+        } else if (!vlanPriority.equals(other.vlanPriority))
+            return false;
+        if (idleTimeout == null) {
+            if (other.idleTimeout != null)
+                return false;
+        } else if (!idleTimeout.equals(other.idleTimeout))
+            return false;
+        if (hardTimeout == null) {
+            if (other.hardTimeout != null)
+                return false;
+        } else if (!hardTimeout.equals(other.hardTimeout))
+            return false;
+        return true;
+    }
+
+    public InetAddress getNextHopAddressForL2RWAction() {
+        if (actions != null) {
+            Matcher sstr;
+            for (String actiongrp : actions) {
+                sstr = Pattern.compile("SET_NEXT_HOP=(.*)").matcher(actiongrp);
+                if (sstr.matches()) {
+                    SetNextHopType setNHType = SetNextHopType.CISCO_EXTENSION;
+                    String nextHopInfo = sstr.group(1);
+                    String values[] = nextHopInfo.split("//");
+                    String address = values[0].trim();
+                    String type = null;
+                    if (values.length > 1) {
+                        type = values[1].trim();
+                    }
+
+                    if (type != null) {
+                        for (SetNextHopType nh : SetNextHopType.values()) {
+                            if (nh.equals(type))
+                                setNHType = nh;
+                        }
+                    }
+
+                    log.debug("Get Nexthop address = " + address + " Type = "
+                            + setNHType.toString());
+                    if (setNHType == SetNextHopType.RESOLVE_L2RW) {
+                        try {
+                            return InetAddress.getByName(address);
+                        } catch (Exception e) {
+                            log
+                                    .debug("Exception during nextHopAddress resolution : "
+                                            + e.getMessage());
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getNextHopL2RWUsageError() {
+        return "Could not resolve NextHop IP Address for the selected Switch.<br>"
+                + "Please Check the following configurations.<br>"
+                + "1. Is the NextHop IP address directly connected to the Selected Switch<br>"
+                + "2. If appropriate Subnet Configurations are done in the Switch Manager<br>"
+                + "3. If the Nexthop IP-Address is Correct";
+    }
+
+    public boolean isL2AddressValid(String mac) {
+        if (mac == null) {
+            return false;
+        }
+
+        Pattern macPattern = Pattern
+                .compile("([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}");
+        Matcher mm = macPattern.matcher(mac);
+        if (!mm.matches()) {
+            log
+                    .debug(
+                            "Ethernet address {} is not valid. Example: 00:05:b9:7c:81:5f",
+                            mac);
+            return false;
+        }
+        return true;
+    }
+
+    public boolean isPortValid(Switch sw, Short port) {
+        if (port < 1) {
+            log.debug("port {} is not valid", port);
+            return false;
+        }
+
+        if (sw == null) {
+            log
+                    .debug("switch info is not available. Skip checking if port is part of a switch or not.");
+            return true;
+        }
+
+        Set<NodeConnector> nodeConnectorSet = sw.getNodeConnectors();
+        for (NodeConnector nodeConnector : nodeConnectorSet) {
+            if (((Short) nodeConnector.getID()).equals(port)) {
+                return true;
+            }
+        }
+        log.debug("port {} is not a valid port of node {}", port, sw.getNode());
+        return false;
+    }
+
+    public boolean isVlanIdValid(String vlanId) {
+        int vlan = Integer.decode(vlanId);
+        return ((vlan >= 0) && (vlan < 4096));
+    }
+
+    public boolean isVlanPriorityValid(String vlanPriority) {
+        int pri = Integer.decode(vlanPriority);
+        return ((pri >= 0) && (pri < 8));
+    }
+
+    public boolean isTOSBitsValid(String tosBits) {
+        int tos = Integer.decode(tosBits);
+        return ((tos >= 0) && (tos < 64));
+    }
+
+    public boolean isTpPortValid(String tpPort) {
+        int port = Integer.decode(tpPort);
+        return ((port > 0) && (port <= 0xffff));
+    }
+
+    public boolean isTimeoutValid(String timeout) {
+        int to = Integer.decode(timeout);
+        return ((to >= 0) && (to <= 0xffff));
+    }
+
+    private boolean conflictWithContainerFlow(IContainer container,
+            StringBuffer resultStr) {
+        // Return true if it's default container
+        if (container.getName().equals(GlobalConstants.DEFAULT.toString())) {
+            return false;
+        }
+
+        // No container flow = no conflict
+        List<ContainerFlow> cFlowList = container.getContainerFlows();
+        if (((cFlowList == null)) || cFlowList.isEmpty()) {
+            return false;
+        }
+
+        // Check against each container's flow
+        Flow flow = this.getFlow();
+
+        // Configuration is rejected if it conflicts with _all_ the container flows
+        for (ContainerFlow cFlow : cFlowList) {
+            if (cFlow.allowsFlow(flow)) {
+                log
+                        .trace("Config is congruent with at least one container flow");
+                return false;
+            }
+        }
+        String msg = "Flow Config conflicts with all existing container flows";
+        resultStr.append(msg);
+        log.trace(msg);
+
+        return true;
+    }
+
+    public boolean isValid(IContainer container, StringBuffer resultStr) {
+        EtherIPType etype = EtherIPType.ANY;
+        EtherIPType ipsrctype = EtherIPType.ANY;
+        EtherIPType ipdsttype = EtherIPType.ANY;
+
+        String containerName = (container == null) ? GlobalConstants.DEFAULT
+                .toString() : container.getName();
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+
+        Switch sw = null;
+        try {
+            if (name == null) {
+                resultStr.append(String.format("Name is null"));
+                return false;
+            }
+            if (node == null) {
+                resultStr.append(String.format("Node is null"));
+                return false;
+            }
+            if (switchManager != null) {
+                for (Switch device : switchManager.getNetworkDevices()) {
+                    if (device.getNode().equals(node)) {
+                        sw = device;
+                        break;
+                    }
+                }
+                if (sw == null) {
+                    resultStr
+                            .append(String.format("Node %s not found", node));
+                    return false;
+                }
+            } else {
+                log.debug("switchmanager is not set yet");
+            }
+
+            if (priority != null) {
+                if (Integer.decode(priority) < 0
+                        || (Integer.decode(priority) > 65535)) {
+                    resultStr.append(String.format(
+                            "priority %s is not in the range 0 - 65535",
+                            priority));
+                    return false;
+                }
+            }
+
+            // make sure it's a valid number
+            if (cookie != null)
+                Long.decode(cookie);
+
+            if (ingressPort != null) {
+                Short port = Short.decode(ingressPort);
+                if (isPortValid(sw, port) == false) {
+                    resultStr
+                            .append(String
+                                    .format(
+                                            "Ingress port %d is not valid for the Switch",
+                                            port));
+                    if ((container != null)
+                            && !container.getName().equals(
+                                    GlobalConstants.DEFAULT.toString())) {
+                        resultStr
+                                .append(" in Container " + container.getName());
+                    }
+                    return false;
+                }
+            }
+
+            if ((vlanId != null) && !isVlanIdValid(vlanId)) {
+                resultStr.append(String.format(
+                        "Vlan ID %s is not in the range 0 - 4095", vlanId));
+                return false;
+            }
+
+            if ((vlanPriority != null) && !isVlanPriorityValid(vlanPriority)) {
+                resultStr.append(String.format(
+                        "Vlan priority %s is not in the range 0 - 7",
+                        vlanPriority));
+                return false;
+            }
+
+            if (etherType != null) {
+                int type = Integer.decode(etherType);
+                if ((type < 0) || (type > 0xffff)) {
+                    resultStr.append(String.format(
+                            "Ethernet type %s is not valid", etherType));
+                    return false;
+                } else {
+                    if (type == 0x800)
+                        etype = EtherIPType.V4;
+                    else if (type == 0x86dd)
+                        etype = EtherIPType.V6;
+                }
+            }
+
+            if ((tosBits != null) && !isTOSBitsValid(tosBits)) {
+                resultStr.append(String.format(
+                        "IP ToS bits %s is not in the range 0 - 63", tosBits));
+                return false;
+            }
+
+            if ((tpSrc != null) && !isTpPortValid(tpSrc)) {
+                resultStr.append(String.format(
+                        "Transport source port %s is not valid", tpSrc));
+                return false;
+            }
+            if ((tpDst != null) && !isTpPortValid(tpDst)) {
+                resultStr.append(String.format(
+                        "Transport destination port %s is not valid", tpDst));
+                return false;
+            }
+
+            if ((dlSrc != null) && !isL2AddressValid(dlSrc)) {
+                resultStr
+                        .append(String
+                                .format(
+                                        "Ethernet source address %s is not valid. Example: 00:05:b9:7c:81:5f",
+                                        dlSrc));
+                return false;
+            }
+
+            if ((dlDst != null) && !isL2AddressValid(dlDst)) {
+                resultStr
+                        .append(String
+                                .format(
+                                        "Ethernet destination address %s is not valid. Example: 00:05:b9:7c:81:5f",
+                                        dlDst));
+                return false;
+            }
+
+            if (nwSrc != null) {
+                if (NetUtils.isIPv4AddressValid(nwSrc)) {
+                    ipsrctype = EtherIPType.V4;
+                } else if (NetUtils.isIPv6AddressValid(nwSrc)) {
+                    ipsrctype = EtherIPType.V6;
+                } else {
+                    resultStr.append(String.format(
+                            "IP source address %s is not valid", nwSrc));
+                    return false;
+                }
+            }
+
+            if (nwDst != null) {
+                if (NetUtils.isIPv4AddressValid(nwDst)) {
+                    ipdsttype = EtherIPType.V4;
+                } else if (NetUtils.isIPv6AddressValid(nwDst)) {
+                    ipdsttype = EtherIPType.V6;
+                } else {
+                    resultStr.append(String.format(
+                            "IP destination address %s is not valid", nwDst));
+                    return false;
+                }
+            }
+
+            if (etype != EtherIPType.ANY) {
+                if ((ipsrctype != EtherIPType.ANY) && (ipsrctype != etype)) {
+                    resultStr.append(String
+                            .format("Type mismatch between Ethernet & Src IP"));
+                    return false;
+                }
+                if ((ipdsttype != EtherIPType.ANY) && (ipdsttype != etype)) {
+                    resultStr.append(String
+                            .format("Type mismatch between Ethernet & Dst IP"));
+                    return false;
+                }
+            }
+            if (ipsrctype != ipdsttype) {
+                if (!((ipsrctype == EtherIPType.ANY) || (ipdsttype == EtherIPType.ANY))) {
+                    resultStr
+                            .append(String.format("IP Src Dest Type mismatch"));
+                    return false;
+                }
+            }
+
+            if ((idleTimeout != null) && !isTimeoutValid(idleTimeout)) {
+                resultStr.append(String.format(
+                        "Idle Timeout value %s is not valid", idleTimeout));
+                return false;
+            }
+
+            if ((hardTimeout != null) && !isTimeoutValid(hardTimeout)) {
+                resultStr.append(String.format(
+                        "Hard Timeout value %s is not valid", hardTimeout));
+                return false;
+            }
+
+            Matcher sstr;
+            if (actions != null && !actions.isEmpty()) {
+                for (String actiongrp : actions) {
+                    // check output ports
+                    sstr = Pattern.compile("OUTPUT=(.*)").matcher(actiongrp);
+                    if (sstr.matches()) {
+                        for (String t : sstr.group(1).split(",")) {
+                            Matcher n = Pattern.compile("(?:(\\d+))")
+                                    .matcher(t);
+                            if (n.matches()) {
+                                if (n.group(1) != null) {
+                                    Short port = Short.parseShort(n.group(1));
+                                    if (isPortValid(sw, port) == false) {
+                                        resultStr
+                                                .append(String
+                                                        .format(
+                                                                "Output port %d is not valid for this switch",
+                                                                port));
+                                        if ((container != null)
+                                                && !container.getName().equals(
+                                                        GlobalConstants.DEFAULT
+                                                                .toString())) {
+                                            resultStr.append(" in Container "
+                                                    + container.getName());
+                                        }
+                                        return false;
+                                    }
+                                }
+                            }
+                        }
+                        continue;
+                    }
+                    // Check src IP
+                    sstr = Pattern.compile(ActionType.FLOOD.toString())
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if (container != null) {
+                            resultStr.append(String.format(
+                                    "flood is not allowed in container %s",
+                                    container.getName()));
+                            return false;
+                        }
+                        continue;
+                    }
+                    // Check src IP
+                    sstr = Pattern.compile(
+                            ActionType.SET_NW_SRC.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if (!NetUtils.isIPv4AddressValid(sstr.group(1))) {
+                            resultStr.append(String.format(
+                                    "IP source address %s is not valid", sstr
+                                            .group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+                    // Check dst IP
+                    sstr = Pattern.compile(
+                            ActionType.SET_NW_DST.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if (!NetUtils.isIPv4AddressValid(sstr.group(1))) {
+                            resultStr.append(String.format(
+                                    "IP destination address %s is not valid",
+                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_VLAN_ID.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isVlanIdValid(sstr.group(1))) {
+                            resultStr.append(String.format(
+                                    "Vlan ID %s is not in the range 0 - 4095",
+                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_VLAN_PCP.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isVlanPriorityValid(sstr.group(1))) {
+                            resultStr
+                                    .append(String
+                                            .format(
+                                                    "Vlan priority %s is not in the range 0 - 7",
+                                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_DL_SRC.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isL2AddressValid(sstr.group(1))) {
+                            resultStr
+                                    .append(String
+                                            .format(
+                                                    "Ethernet source address %s is not valid. Example: 00:05:b9:7c:81:5f",
+                                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_DL_DST.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isL2AddressValid(sstr.group(1))) {
+                            resultStr
+                                    .append(String
+                                            .format(
+                                                    "Ethernet destination address %s is not valid. Example: 00:05:b9:7c:81:5f",
+                                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_NW_TOS.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isTOSBitsValid(sstr.group(1))) {
+                            resultStr
+                                    .append(String
+                                            .format(
+                                                    "IP ToS bits %s is not in the range 0 - 63",
+                                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_TP_SRC.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isTpPortValid(sstr.group(1))) {
+                            resultStr.append(String.format(
+                                    "Transport source port %s is not valid",
+                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+
+                    sstr = Pattern.compile(
+                            ActionType.SET_TP_DST.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        if ((sstr.group(1) != null)
+                                && !isTpPortValid(sstr.group(1))) {
+                            resultStr
+                                    .append(String
+                                            .format(
+                                                    "Transport destination port %s is not valid",
+                                                    sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+                    sstr = Pattern.compile(
+                            ActionType.SET_NEXT_HOP.toString() + "=(.*)")
+                            .matcher(actiongrp);
+                    if (sstr.matches()) {
+                        String nextHopInfo = sstr.group(1);
+                        String values[] = nextHopInfo.split("//");
+                        String address = values[0].trim();
+
+                        if ((address == null) || !isOutputNextHopValid(address)) {
+                            resultStr.append(String.format(
+                                    "next hop %s is not valid", sstr.group(1)));
+                            return false;
+                        }
+                        continue;
+                    }
+                }
+            }
+            // Check against the container flow
+            if ((container != null)
+                    && conflictWithContainerFlow(container, resultStr)) {
+                return false;
+            }
+        } catch (NumberFormatException e) {
+            resultStr.append(String.format("Invalid number format %s", e
+                    .getMessage()));
+            return false;
+        }
+
+        return true;
+    }
+
+    public FlowEntry getFlowEntry() {
+        return new FlowEntry(FlowConfig.staticFlowsGroup, this.name, this
+                .getFlow(), this.getNode());
+    }
+
+    public Flow getFlow() {
+        Match match = new Match();
+
+        if (this.ingressPort != null) {
+            match.setField(MatchType.IN_PORT, NodeConnectorCreator
+                    .createOFNodeConnector(Short.parseShort(ingressPort),
+                            getNode()));
+        }
+        if (this.dlSrc != null) {
+            match.setField(MatchType.DL_SRC, HexEncode
+                    .bytesFromHexString(this.dlSrc));
+        }
+        if (this.dlDst != null) {
+            match.setField(MatchType.DL_DST, HexEncode
+                    .bytesFromHexString(this.dlDst));
+        }
+        if (this.etherType != null) {
+            match.setField(MatchType.DL_TYPE, Integer.decode(etherType)
+                    .shortValue());
+        }
+        if (this.vlanId != null) {
+            match.setField(MatchType.DL_VLAN, Short.parseShort(this.vlanId));
+        }
+        if (this.vlanPriority != null) {
+            match.setField(MatchType.DL_VLAN_PR, Byte
+                    .parseByte(this.vlanPriority));
+        }
+        if (this.nwSrc != null) {
+            String parts[] = this.nwSrc.split("/");
+            InetAddress ip = NetUtils.parseInetAddress(parts[0]);
+            InetAddress mask = null;
+            if (parts.length > 1) {
+                int maskLen = Integer.parseInt(parts[1]);
+                mask = NetUtils.getInetNetworkMask(maskLen,
+                        ip instanceof Inet6Address);
+            }
+            match.setField(MatchType.NW_SRC, ip, mask);
+        }
+        if (this.nwDst != null) {
+            String parts[] = this.nwDst.split("/");
+            InetAddress ip = NetUtils.parseInetAddress(parts[0]);
+            InetAddress mask = null;
+            if (parts.length > 1) {
+                int maskLen = Integer.parseInt(parts[1]);
+                mask = NetUtils.getInetNetworkMask(maskLen,
+                        ip instanceof Inet6Address);
+            }
+            match.setField(MatchType.NW_DST, ip, mask);
+        }
+        if (this.protocol != null) {
+            match.setField(MatchType.NW_PROTO, IPProtocols
+                    .getProtocolNumberByte(this.protocol));
+        }
+        if (this.tosBits != null) {
+            match.setField(MatchType.NW_TOS, Byte.parseByte(this.tosBits));
+        }
+        if (this.tpSrc != null) {
+            match.setField(MatchType.TP_SRC, Integer.valueOf(this.tpSrc)
+                    .shortValue());
+        }
+        if (this.tpDst != null) {
+            match.setField(MatchType.TP_DST, Integer.valueOf(this.tpDst)
+                    .shortValue());
+        }
+
+        Flow flow = new Flow(match, getActionList());
+        if (this.cookie != null) {
+            flow.setId(Long.parseLong(cookie));
+        }
+        if (this.hardTimeout != null) {
+            flow.setHardTimeout(Short.parseShort(this.hardTimeout));
+        }
+        if (this.idleTimeout != null) {
+            flow.setIdleTimeout(Short.parseShort(idleTimeout));
+        }
+        if (this.priority != null) {
+            flow.setPriority(Integer.decode(this.priority).shortValue());
+        }
+        return flow;
+    }
+
+    public boolean isOutputNextHopValid(String onh) {
+        if (onh == null) {
+            return false;
+        }
+        /*
+         * For now, only takes IPv4 or IPv6 address
+         */
+        return (NetUtils.isIPv4AddressValid(onh) || NetUtils
+                .isIPv6AddressValid(onh));
+    }
+
+    public boolean isByNameAndNodeIdEqual(FlowConfig that) {
+        return (this.name.equals(that.name) && this.node.equals(that.node));
+    }
+
+    public boolean isByNameAndNodeIdEqual(String name, Node node) {
+        return (this.name.equals(name) && this.node.equals(node));
+    }
+
+    public boolean onNode(Node node) {
+        return this.node.equals(node);
+    }
+
+    public static List<String> getSupportedNextHopTypes() {
+        List<String> s = new ArrayList<String>();
+        for (SetNextHopType nh : SetNextHopType.values()) {
+            s.add(nh.toString());
+        }
+        return s;
+    }
+
+    public void toggleStatus() {
+        installInHw = (installInHw == null) ? "true" : (installInHw
+                .equals("true")) ? "false" : "true";
+    }
+
+    /*
+     * Parses the actions string and return the List of SAL Action
+     * No syntax check run, as this function will be called when the
+     * config validation check has already been performed
+     */
+    private List<Action> getActionList() {
+        List<Action> actionList = new ArrayList<Action>();
+
+        if (actions != null) {
+            Matcher sstr;
+            for (String actiongrp : actions) {
+                sstr = Pattern.compile(ActionType.OUTPUT + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    for (String t : sstr.group(1).split(",")) {
+                        Matcher n = Pattern.compile("(?:(\\d+))").matcher(t);
+                        if (n.matches()) {
+                            if (n.group(1) != null) {
+                                short ofPort = Short.parseShort(n.group(1));
+                                actionList.add(new Output(NodeConnectorCreator
+                                        .createOFNodeConnector(ofPort, this
+                                                .getNode())));
+                            }
+                        }
+                    }
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.DROP.toString()).matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new Drop());
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.LOOPBACK.toString()).matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new Loopback());
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.FLOOD.toString()).matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new Flood());
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.SW_PATH.toString()).matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SwPath());
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.HW_PATH.toString()).matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new HwPath());
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.CONTROLLER.toString())
+                        .matcher(actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new Controller());
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_VLAN_ID.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SetVlanId(Short
+                            .parseShort(sstr.group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_VLAN_PCP.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList
+                            .add(new SetVlanPcp(Byte.parseByte(sstr.group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(ActionType.POP_VLAN.toString()).matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new PopVlan());
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_DL_SRC.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SetDlSrc(HexEncode
+                            .bytesFromHexString(sstr.group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_DL_DST.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SetDlDst(HexEncode
+                            .bytesFromHexString(sstr.group(1))));
+                    continue;
+                }
+                sstr = Pattern.compile(
+                        ActionType.SET_NW_SRC.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SetNwSrc(NetUtils.parseInetAddress(sstr
+                            .group(1))));
+                    continue;
+                }
+                sstr = Pattern.compile(
+                        ActionType.SET_NW_DST.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SetNwDst(NetUtils.parseInetAddress(sstr
+                            .group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_NW_TOS.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList.add(new SetNwTos(Byte.parseByte(sstr.group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_TP_SRC.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList
+                            .add(new SetTpSrc(Integer.valueOf(sstr.group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_TP_DST.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    actionList
+                            .add(new SetTpDst(Integer.valueOf(sstr.group(1))));
+                    continue;
+                }
+
+                sstr = Pattern.compile(
+                        ActionType.SET_NEXT_HOP.toString() + "=(.*)").matcher(
+                        actiongrp);
+                if (sstr.matches()) {
+                    log.warn("We do not handle next hop action yet....");
+                    continue;
+                }
+            }
+        }
+        return actionList;
+    }
+
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntry.java
new file mode 100644 (file)
index 0000000..2ab1a05
--- /dev/null
@@ -0,0 +1,137 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+
+/**
+ * Represents a flow applications request Forwarding Rules Manager to install
+ * on a network node. A FlowEntry is constituted of a flow (match + actions),
+ * the target network node, and the flow name. It also includes a group name. 
+ * For instance the flows constituting a policy all share the same group name.
+ */
+public class FlowEntry implements Cloneable, Serializable {
+    private static final long serialVersionUID = 1L;
+    private String groupName; // group name
+    private String flowName; // flow name (may be null)
+    private Node node; // network node where to install the flow
+    private Flow flow; // match + action
+
+    public FlowEntry(String groupName, String flowName, Flow flow, Node node) {
+        this.groupName = groupName;
+        this.flow = flow;
+        this.node = node;
+        this.flowName = (flowName != null) ? flowName : constructFlowName();
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public void setGroupName(String name) {
+        this.groupName = name;
+    }
+
+    /**
+     * Return the actual Flow contained in this entry
+     *
+     * @return the flow
+     */
+    public Flow getFlow() {
+        return flow;
+    }
+
+    public Node getNode() {
+        return node;
+    }
+
+    public void setNode(Node n) {
+        this.node = n;
+    }
+
+    public String getFlowName() {
+        return flowName;
+    }
+
+    public void setFlowName(String n) {
+        this.flowName = n;
+    }
+
+    @Override
+    public FlowEntry clone() {
+        FlowEntry cloned = null;
+        try {
+            cloned = (FlowEntry) super.clone();
+            cloned.flow = this.flow.clone();
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+        }
+        return cloned;
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public String toString() {
+        return "FlowEntry[flowName = " + flowName + ", groupName = "
+                + groupName + ",node = " + node + ", flow = " + flow + "]";
+    }
+
+    private String constructFlowName() {
+        return this.groupName + "_" + new Date().toString();
+    }
+
+    public boolean equalsByNodeAndName(Node node, String flowName) {
+        return this.node.equals(node) && this.flowName.equals(flowName);
+    }
+
+    /**
+     * Merges the current Flow with the passed Container Flow
+     *
+     * Note: Container Flow merging is not an injective function.
+     * Be m1 and m2 two different matches, and be f() the flow merge
+     * function, such that y1 = f(m1) and y2 = f(m2) are the two merged
+     * matches, we may have: y1 = y2
+     *
+     *
+     * @param containerFlow
+     * @return this merged FlowEntry
+     */
+    public FlowEntry mergeWith(ContainerFlow containerFlow) {
+        Match myMatch = flow.getMatch();
+
+        // Based on this flow direction, rearrange the match
+        Match match = containerFlow.getMatch();
+
+        // Merge
+        myMatch.mergeWithFilter(match);
+
+        // Replace this Flow's match with merged version
+        flow.setMatch(myMatch);
+
+        return this;
+    }
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntryInstall.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/FlowEntryInstall.java
new file mode 100644 (file)
index 0000000..ac0a4cc
--- /dev/null
@@ -0,0 +1,93 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.Node;
+
+/**
+ * The flow database object representing the flow entry to install on
+ * the network node. It contains the original flow entry FRM was
+ * requested to install, the container flow with which that entry had
+ * to be merged and the resultant merged flow entry, which is the
+ * one that was eventually installed on the network node
+ *
+ * Note: If the container flow is null, the install entry will be a clone
+ * of the original entry
+ *
+ */
+public class FlowEntryInstall {
+    private FlowEntry original;
+    private ContainerFlow cFlow;
+    private FlowEntry install;
+    transient private boolean deletePending;
+
+    public FlowEntryInstall(FlowEntry original, ContainerFlow cFlow) {
+        this.original = original;
+        this.cFlow = cFlow;
+        this.install = (cFlow == null) ? original.clone() : original
+                .mergeWith(cFlow);
+        deletePending = false;
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public String getFlowName() {
+        return original.getFlowName();
+    }
+
+    public String getGroupName() {
+        return original.getGroupName();
+    }
+
+    public Node getNode() {
+        return original.getNode();
+    }
+
+    public boolean equalsByNodeAndName(Node node, String flowName) {
+        return original.equalsByNodeAndName(node, flowName);
+    }
+
+    public FlowEntry getOriginal() {
+        return original;
+    }
+
+    public ContainerFlow getContainerFlow() {
+        return cFlow;
+    }
+
+    public FlowEntry getInstall() {
+        return install;
+    }
+
+    public boolean isDeletePending() {
+        return deletePending;
+    }
+
+    public void toBeDeleted() {
+        this.deletePending = true;
+    }
+
+    @Override
+    public String toString() {
+        return "[Install = " + install + " Original: " + original + " cFlow: "
+                + cFlow + "]";
+    }
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManager.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManager.java
new file mode 100644 (file)
index 0000000..3a1250b
--- /dev/null
@@ -0,0 +1,267 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * Interface that describes methods for installing or removing forwarding rules
+ * and to access to the flows database.
+ *
+ */
+public interface IForwardingRulesManager {
+
+    /**
+     * It requests FRM to install the passed Flow Entry. FRM will request
+     * the SDN protocol plugin to install the flow on the network node.
+     * Based on the result of this operation FRM will update its database
+     * accordingly and will return the proper {@code Status} code.
+     * 
+        * @param flow the flow entry to install
+        * @return the {@code Status} object indicating the result of this action.
+        */
+    public Status installFlowEntry(FlowEntry flow);
+
+    /**
+     * It requests FRM to remove the passed Flow Entry. FRM will request
+     * the SDN protocol plugin to uninstall the flow from the network node.
+     * Based on the result of this operation FRM will update its database
+     * accordingly and will return the proper {@code Status} code.
+     * 
+     * @param flow the flow entry to uninstall
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status uninstallFlowEntry(FlowEntry flow);
+
+    /**
+     * It requests FRM to replace the currently installed Flow Entry with the
+     * new one. It is up to the SDN protocol plugin to decide how to convey
+     * this message to the network node. It could be a delete + add or a single
+     * modify message depending on the SDN protocol specifications
+     * If the current flow is equal to the new one  it will be a no op and
+     * success code is returned.
+     * 
+     * @param current the current flow entry to modify
+     * @param newone the new flow entry which will replace the current one
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status modifyFlowEntry(FlowEntry current, FlowEntry newone);
+
+    /**
+     * It requests the FRM to replace the currently installed Flow Entry with
+     * the new one. The currently installed entry is derived by the Match
+     * portion of the passed Flow. FRM looks in its database for a previously
+     * installed FlowEntry which Match equals the Match of the passed Flow.
+     * If it finds it, it will request the SDN protocol plugin to replace the
+     * existing flow with the new one on the network node. If it does not
+     * find it, it will request plugin to add the new flow. If the passed entry
+     * is not valid an error code is returned.
+     * If the existing flow is equal to the passed one it will be a no op and
+     * success code is returned.
+     * 
+     * @param newone the new flow entry to install
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status modifyOrAddFlowEntry(FlowEntry newone);
+
+    /**
+     * Check whether the passed flow entry conflicts with the Container flows
+     * 
+     * @param flow the flow entry to test
+     * @return true if conflicts, false otherwise
+     */
+    public boolean checkFlowEntryConflict(FlowEntry flow);
+
+    /**
+     * Returns the list of Flow entries across network nodes which are part of the
+     * same flow group, policy
+     *
+     * @param group the group name
+     * @return the list of flow entries belonging to the specified group
+     */
+    public List<FlowEntry> getFlowEntriesForGroup(String group);
+
+    /**
+     * Add a list of output port to the flow with the specified name on the specified network node
+     *
+     * @param node     the network node
+     * @param flowName the flow name
+     * @param dstPort the list of ports to be added to the flow output actions
+     */
+    public void addOutputPort(Node node, String flowName,
+            List<NodeConnector> dstPort);
+
+    /**
+     * Remove a list of output port from the flow with the specified name on the specified network node
+     *
+     * @param node the network node
+     * @param flowName the flow name
+     * @param dstPortthe list of ports to be removed from the flow output actions
+     */
+    public void removeOutputPort(Node node, String flowName,
+            List<NodeConnector> dstPort);
+
+    /**
+     * Replace the current output port in the specified flow with the specified one
+     *
+     * @param node     the network node
+     * @param groupName the group name
+     * @param flowName the flow name
+     * @param dstPort  the new output action port
+     */
+    public void replaceOutputPort(Node node, String flowName,
+            NodeConnector outPort);
+
+    /**
+     * Returns the output port configured on the specified flow
+     *
+     * @param node     the network node
+     * @param flowName the flow name
+     * @return the output action port for the specified flow
+     */
+    public NodeConnector getOutputPort(Node node, String flowName);
+
+    /**
+     * Returns all the troubleshooting information that applications
+     * have set along with the policy they have configured through
+     * forwarding rules manger.
+     * 
+     * @return the collection of troubleshooting objects
+     */
+    public Map<String, Object> getTSPolicyData();
+
+    /**
+     * Set the troubleshooting information for the policy
+     *
+     * @param policyname the flow group name
+     * @param o        the object containing the troubleshooting information
+     * @param add      true for adding, false for removing
+     */
+    public void setTSPolicyData(String policyName, Object o, boolean add);
+
+    /**
+     * Returns the troubleshooting information that was set for the specified policy
+     * 
+     * @param groupName the flows group name
+     * @return the troubleshooting info object
+     */
+    public Object getTSPolicyData(String policyName);
+
+    /**
+     * Returns the specifications of all the flows configured for all the 
+     * switches on the current container
+     *
+     * @return the list of flow configurations present in the database
+     */
+    public List<FlowConfig> getStaticFlows();
+
+    /**
+     * Returns the specifications of all the flows configured for 
+     * the given switch on the current container
+     *
+     * @param node     the network node identifier
+     * @return the list of {@code FlowConfig} objects
+     */
+    public List<FlowConfig> getStaticFlows(Node node);
+
+    /**
+     * Returns the specification of the flow configured for the given
+     * network node on the current container
+     *
+     * @param name the flow name
+     * @param n the netwrok node identifier
+     * @return the {@code FlowConfig} object
+     */
+    public FlowConfig getStaticFlow(String name, Node n);
+
+    /**
+     * Returns the list of names of flows configured for the given 
+     * Network node on the current container
+     *
+     * @param node the network node identifier
+     * @return the list of flow names
+     */
+    public List<String> getStaticFlowNamesForNode(Node node);
+
+    /**
+     * Returns the list of Node(s) for which a static flow has been configured
+     *
+     * @return the list of network nodes
+     */
+    public List<Node> getListNodeWithConfiguredFlows();
+
+    /**
+     * Save the flow configured so far to file
+     * 
+     * @return the {@code Status} object indicating the result of this action.
+     */
+    public Status saveConfig();
+
+    /**
+     * Add a flow specified by the {@code FlowConfig} object on the current container
+     * 
+     * @param config the {@code FlowConfig} object representing the static flow
+     * @param restore if set to true, the config object validation will be skipped.
+     *                                  Used only internally, always set it to false.
+     * @return the {@code Status} object indicating the result of this action.
+     */
+    public Status addStaticFlow(FlowConfig config, boolean restore);
+
+    /**
+     * Remove a flow specified by the {@code FlowConfig} object on the current container
+     * 
+     * @param config the {@code FlowConfig} object representing the static flow
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status removeStaticFlow(FlowConfig config);
+
+    /**
+     * Replace the flow identified by the {@code FlowConfig.name} name for
+     * the {@code FlowConfig.node} network node with the new flow specified
+     * by {@code FlowConfig} object
+     *
+     * @param config the {@code FlowConfig} object
+     * @returnthe {@code Status} object indicating the result of this action
+     */
+    public Status modifyStaticFlow(FlowConfig config);
+
+    /**
+     * Remove the flow specified by name on the passed network node
+     *
+     * @param name for the static flow
+     * @param node on which the flow is attached
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status removeStaticFlow(String name, Node node);
+
+    /**
+     * Toggle the installation status of the specified configured flow
+     * If the flow configuration status is active, this call will
+     * change the flow status to inactive and vice-versa
+     *
+     * @param configObject the {@code FlowConfig} object
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status toggleStaticFlowStatus(FlowConfig configObject);
+
+    public Map<String, PortGroupConfig> getPortGroupConfigs();
+
+    public boolean addPortGroupConfig(String name, String regex, boolean load);
+
+    public boolean delPortGroupConfig(String name);
+
+    public PortGroupProvider getPortGroupProvider();
+
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManagerAware.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManagerAware.java
new file mode 100644 (file)
index 0000000..df37063
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+
+/**
+ * The interface which describes the methods forwarding rules manager
+ * will call for notifying the listeners of policy installation updates.
+ *
+ */
+public interface IForwardingRulesManagerAware {
+
+       /**
+        * Inform the listeners that a troubleshooting information was 
+        * added or removed for the specified policy.
+        * 
+        * @param policyName the policy affected
+        * @param add true if the troubleshooting information was added, false otherwise
+        */
+    public void policyUpdate(String policyName, boolean add);
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroup.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroup.java
new file mode 100644 (file)
index 0000000..1e457be
--- /dev/null
@@ -0,0 +1,100 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * PortGroup is a simple data-structure to represent any arbitrary group of ports
+ * on a Switch (that is represented using its switch-ID).
+ *
+ * PortGroup is used by PortGroupProvider application to signal a set of ports that
+ * represent a configured PortGroupConfig.
+ *
+ *
+ */
+public class PortGroup {
+    private long matrixSwitchId;
+    private Set<Short> ports;
+
+    /**
+     * PortGroup Constructor using Switch and Ports.
+     *
+     * @param matrixSwitchId Switch Id that represents an openflow Switch
+     * @param ports Set of short values representing openflow port-ids.
+     */
+    public PortGroup(long matrixSwitchId, Set<Short> ports) {
+        super();
+        this.matrixSwitchId = matrixSwitchId;
+        this.ports = ports;
+    }
+
+    /**
+     * PortGroup Constructor using Switch.
+     *
+     * @param matrixSwitchId Switch-Id that represents an openflow Switch
+     */
+    public PortGroup(long matrixSwitchId) {
+        this.matrixSwitchId = matrixSwitchId;
+        this.ports = new HashSet<Short>();
+    }
+
+    /**
+     * Returns the switchId representing the Switch that makes this PortGroup.
+     *
+     * @return long switchId
+     */
+    public long getMatrixSwitchId() {
+        return matrixSwitchId;
+    }
+
+    /**
+     * Assigns a Switch to this PortGroup
+     *
+     * @param matrixSwitchId Switch-Id that represents an openflow Switch
+     */
+    public void setMatrixSwitchId(long matrixSwitchId) {
+        this.matrixSwitchId = matrixSwitchId;
+    }
+
+    /**
+     * Returns the Set of Ports that makes this PortGroup.
+     *
+     * @return Set of short values representing openflow port-ids.
+     */
+    public Set<Short> getPorts() {
+        return ports;
+    }
+
+    /**
+     * Assigns a set of openflow ports to this PortGroup
+     *
+     * @param ports Set of short values representing openflow port-ids.
+     */
+    public void setPorts(Set<Short> ports) {
+        this.ports = ports;
+    }
+
+    /**
+     * Adds a port to this PortGroup
+     *
+     * @param port Short value of a openflow port.
+     */
+    public void addPort(short port) {
+        ports.add(port);
+    }
+
+    @Override
+    public String toString() {
+        return "PortGroup [matrixSwitchId=" + matrixSwitchId + ", ports="
+                + ports + "]";
+    }
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupChangeListener.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupChangeListener.java
new file mode 100644 (file)
index 0000000..3479f29
--- /dev/null
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.util.Map;
+
+import org.opendaylight.controller.sal.core.Node;
+
+/**
+ * PortGroupChangeListener listens to the PortGroup updates provided by the PortGroupProvider.
+ *
+ *
+ */
+public interface PortGroupChangeListener {
+    /**
+     * This method is invoked by PortGroupProvider whenever it detects a change in PortGroup
+     * membership for a given PortGroupConfig.
+     *
+     * @param config Port Group Configuration
+     * @param portGroupData HashMap of Node id to PortGroup that represents the updated ports as detected by PortGroupProvider.
+     * @param add true indicates that the PortGroup is added. False indicates that the PortGroup is removed.
+     */
+    void portGroupChanged(PortGroupConfig config,
+            Map<Node, PortGroup> portGroupData, boolean add);
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupConfig.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupConfig.java
new file mode 100644 (file)
index 0000000..39683d4
--- /dev/null
@@ -0,0 +1,158 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * PortGroupConfig class represents the User's Configuration with a Opaque Regular Expression
+ * String that is parsed and handled by PortGroupProvider.
+ *
+ * Typically, the opaque matchString will be a Regular Expression String supported by a particular
+ * PortGroupProvider based on Customer requirements.
+ *
+ *
+ *
+ */
+public class PortGroupConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final String prettyFields[] = { "Name", "Match Criteria" };
+
+    private String name;
+    private String matchString;
+
+    /**
+     * Default Constructor with regular expression string defaulted to ".*"
+     */
+    public PortGroupConfig() {
+        name = "default";
+        matchString = ".*";
+    }
+
+    /**
+     * Constructor to create a Port Group Configuration using a Group Name and an Opaque
+     * String that is managed by PortGroupProvider.
+     *
+     * @param name Group Name representing a Port Group configuration
+     * @param matchString An Opaque String managed by PortGroupProvider
+     */
+    public PortGroupConfig(String name, String matchString) {
+        super();
+        this.name = name;
+        this.matchString = matchString;
+    }
+
+    /**
+     * Returns the user configured PortGroup Configuration name.
+     *
+     * @return Configuration Name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Assigns a name to the configuration
+     * @param name configuration name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the Opaque string
+     * @return
+     */
+    public String getMatchString() {
+        return matchString;
+    }
+
+    /**
+     * Assigns an opaque String to the Configuration.
+     *
+     * @param matchString Opaque string handled by PortGroupProvider
+     */
+    public void setMatchString(String matchString) {
+        this.matchString = matchString;
+    }
+
+    /**
+     * Returns the names of all the configurable fields in PortGroupConfig.
+     * This method is typically used by NorthBound apis.
+     *
+     * @return List of Field names that can be configured.
+     */
+    public static List<String> getFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (Field fld : PortGroupConfig.class.getDeclaredFields()) {
+            fieldList.add(fld.getName());
+        }
+        //remove static field(s)
+        fieldList.remove(0);
+        fieldList.remove(0);
+
+        return fieldList;
+    }
+
+    /**
+     * Returns the names of all the configurable fields in PortGroupConfig in human readable format for UI purposes.
+     * This method is typically used by Web/UI apis.
+     *
+     * @return List of Human readable Strings that corresponds to the configurable field names.
+     */
+    public static List<String> getPrettyFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (String str : prettyFields) {
+            fieldList.add(str);
+        }
+        return fieldList;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((matchString == null) ? 0 : matchString.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        PortGroupConfig other = (PortGroupConfig) obj;
+        if (matchString == null) {
+            if (other.matchString != null)
+                return false;
+        } else if (!matchString.equals(other.matchString))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "PortGroupConfig [name=" + name + ", matchString=" + matchString
+                + "]";
+    }
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupProvider.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/PortGroupProvider.java
new file mode 100644 (file)
index 0000000..df6618c
--- /dev/null
@@ -0,0 +1,96 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.util.Map;
+
+import org.opendaylight.controller.sal.core.Node;
+
+/**
+ * PortGroupProvider interface provides all the necessary blueprint for a custom application to implement
+ * in order to provide Port Grouping Service. Custom Application that implements this interface will have
+ * to handle the opaque match criteria passed to it via PortGroupConfig.
+ *
+ *
+ *
+ */
+public interface PortGroupProvider {
+    /**
+     * This method is invoked by the Controller towards the Provider when a new port group is configured.
+     *
+     * @param config New PortGroupConfig object created by user Configuration.
+     * @return true if successful. false otherwise.
+     */
+    public boolean createPortGroupConfig(PortGroupConfig config);
+
+    /**
+     * This method is invoked by the Controller towards the Provider when an existing port group is deleted.
+     *
+     * @param config Existing Port Group Configuration deleted by the user.
+     * @return true if successful. false otherwise.
+     */
+    public boolean deletePortGroupConfig(PortGroupConfig config);
+
+    /**
+     * Returns the complete mapping database corresponds to a PortGroup Configuration.
+     * Its the PortGroupProvider Application's responsibility to manage the Switches & the Set of its Ports that
+     * correspond to each of the Configuration and return it to the Controller when requested.
+     *
+     * @param config User Configuration
+     * @see PortGroupConfig
+     * @return Database of Switch-Id to PortGroup mapping that corresponds to the Port Group User Configuration.
+     */
+    public Map<Node, PortGroup> getPortGroupData(PortGroupConfig config);
+
+    /**
+     * Returns PortGroup data for a given Switch and user Configuration.
+     * Its the PortGroupProvider Application's responsibility to manage the Switches & the Set of its Ports that
+     * correspond to each of the Configuration and return it to the Controller when requested.
+     *
+     * @param config User Configuration
+     * @param matrixSwitchId Switch Id that represents an openflow Switch
+     * @see PortGroupConfig
+     * @return PortGroup data for a given Openflow switch.
+     * @see PortGroup
+     */
+    public PortGroup getPortGroupData(PortGroupConfig config,
+            long matrixSwitchId);
+
+    /**
+     * Registers a Listener for Port Group membership changes based on Custom application algorithm.
+     * @param listener A Controller module that listens to events from the Custom Port Grouping Application.
+     */
+    public void registerPortGroupChange(PortGroupChangeListener listener);
+
+    /**
+     * Application returns an Usage string for the Match Criteria User Configuration.
+     * Controller provides an opportunity for application to implement Custom Algorithm for Port Grouping.
+     * This method exposes the custom algorithm to the user so that the user can configure the matchString
+     * regular expression in PortGroupConfig appropriately.
+     *
+     * @return Usage string.
+     */
+    public String getApplicationDrivenMatchCriteriaUsage();
+
+    /**
+     * Returns the name of the Custom Application that implements  PortGroupProvider interface.
+     *
+     * @return Provider Name
+     */
+    public String getProviderName();
+
+    /**
+     * Controller uses this method to check with the Provider supports the matchCriteria String configured by the User.
+     *
+     * @param matchCriteria
+     * @return true if the Provider supports the matchCriteria String. false otherwise.
+     */
+    public boolean isMatchCriteriaSupported(String matchCriteria);
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/Activator.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/Activator.java
new file mode 100644 (file)
index 0000000..9da0970
--- /dev/null
@@ -0,0 +1,136 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager.internal;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManagerAware;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.core.IContainer;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { ForwardingRulesManagerImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(ForwardingRulesManagerImpl.class)) {
+            String interfaces[] = null;
+            Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+            Set<String> propSet = new HashSet<String>();
+            propSet.add("staticFlows");
+            props.put("cachenames", propSet);
+
+            // export the service
+            if (containerName.equals(GlobalConstants.DEFAULT.toString())) {
+                interfaces = new String[] { IContainerListener.class.getName(),
+                        ISwitchManagerAware.class.getName(),
+                        IForwardingRulesManager.class.getName(),
+                        IInventoryListener.class.getName(),
+                        ICacheUpdateAware.class.getName(),
+                        IConfigurationContainerAware.class.getName() };
+            } else {
+                interfaces = new String[] {
+                        ISwitchManagerAware.class.getName(),
+                        IForwardingRulesManager.class.getName(),
+                        IInventoryListener.class.getName(),
+                        ICacheUpdateAware.class.getName(),
+                        IConfigurationContainerAware.class.getName() };
+            }
+
+            c.setInterface(interfaces, props);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IFlowProgrammerService.class).setCallbacks(
+                    "setFlowProgrammerService", "unsetFlowProgrammerService")
+                    .setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IClusterContainerServices.class).setCallbacks(
+                    "setClusterContainerService",
+                    "unsetClusterContainerService").setRequired(true));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManager.class).setCallbacks("setSwitchManager",
+                    "unsetSwitchManager").setRequired(true));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IForwardingRulesManagerAware.class).setCallbacks(
+                    "setFrmAware", "unsetFrmAware").setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IfIptoHost.class).setCallbacks("setHostFinder",
+                    "unsetHostFinder").setRequired(true));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IContainer.class).setCallbacks("setIContainer",
+                    "unsetIContainer").setRequired(true));
+        }
+    }
+}
diff --git a/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManagerImpl.java b/opendaylight/forwardingrulesmanager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManagerImpl.java
new file mode 100644 (file)
index 0000000..08d3412
--- /dev/null
@@ -0,0 +1,2339 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager.internal;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
+import org.opendaylight.controller.forwardingrulesmanager.FlowEntryInstall;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManagerAware;
+import org.opendaylight.controller.forwardingrulesmanager.PortGroup;
+import org.opendaylight.controller.forwardingrulesmanager.PortGroupChangeListener;
+import org.opendaylight.controller.forwardingrulesmanager.PortGroupConfig;
+import org.opendaylight.controller.forwardingrulesmanager.PortGroupProvider;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.ActionType;
+import org.opendaylight.controller.sal.action.Controller;
+import org.opendaylight.controller.sal.action.Flood;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.PopVlan;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainer;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.IObjectReader;
+import org.opendaylight.controller.sal.utils.IPProtocols;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.sal.utils.ObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectWriter;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
+import org.opendaylight.controller.switchmanager.Subnet;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class that manages forwarding rule installation and removal per container of
+ * the network. It also maintains the central repository of all the forwarding
+ * rules installed on the network nodes.
+ */
+public class ForwardingRulesManagerImpl implements IForwardingRulesManager,
+        PortGroupChangeListener, IContainerListener, ISwitchManagerAware,
+        IConfigurationContainerAware, IInventoryListener, IObjectReader,
+        ICacheUpdateAware<Long, String>, CommandProvider {
+    private static final String SAVE = "Save";
+    private static final Logger log = LoggerFactory
+            .getLogger(ForwardingRulesManagerImpl.class);
+    private Map<Long, String> flowsSaveEvent;
+    private String frmFileName;
+    private String portGroupFileName;
+    private ConcurrentMap<Integer, FlowConfig> staticFlows;
+    private ConcurrentMap<Integer, Integer> staticFlowsOrdinal;
+    private ConcurrentMap<String, PortGroupConfig> portGroupConfigs;
+    private ConcurrentMap<PortGroupConfig, Map<Node, PortGroup>> portGroupData;
+    private ConcurrentMap<String, Object> TSPolicies;
+    private boolean inContainerMode; // being used by default instance only
+    /*
+     * Flow database. It's the software view of what was installed on the
+     * switch. It is indexed by node. For convenience a version indexed
+     * by group name is also maintained. The core element is a class which
+     * contains the flow entry pushed by the functional modules and the
+     * respective container flow merged version. In absence of container
+     * flows, the two flow entries are the same.
+     */
+    private ConcurrentMap<Node, Set<FlowEntryInstall>> nodeFlows;
+    private ConcurrentMap<String, Set<FlowEntryInstall>> groupFlows;
+    /*
+     * Inactive flow list. This is for the global instance of FRM
+     * It will contain all the flow entries which were installed on the
+     * global container when the first container is created.
+     */
+    private List<FlowEntry> inactiveFlows;
+
+    private IfIptoHost hostFinder;
+    private IContainer container;
+    private Set<IForwardingRulesManagerAware> frmAware;
+    private PortGroupProvider portGroupProvider;
+    private IFlowProgrammerService programmer;
+    private IClusterContainerServices clusterContainerService = null;
+    private ISwitchManager switchManager;
+
+    /**
+     * Adds a flow entry onto the network node
+     * It runs various validity checks and derive the final container flows
+     * merged entries that will be attempted to be installed
+     *
+     * @param flowEntry the original flow entry application requested to add
+     * @return
+     */
+    private Status addEntry(FlowEntry flowEntry) {
+        // Sanity Check
+        if (flowEntry == null || flowEntry.getNode() == null) {
+               String msg = "Invalid FlowEntry";
+            log.warn(msg + ": " + flowEntry);
+            return new Status(StatusCode.NOTACCEPTABLE, msg);
+        }
+
+        /*
+         * Derive the container flow merged entries to install
+         * In presence of N container flows, we may end up with
+         * N different entries to install...
+         */
+        List<FlowEntryInstall> toInstallList = deriveInstallEntries(flowEntry
+                .clone(), container.getContainerFlows());
+
+        // Container Flow conflict Check
+        if (toInstallList.isEmpty()) {
+               String msg = "Flow Entry conflicts with all Container Flows";
+               log.warn(msg);
+            return new Status(StatusCode.CONFLICT, msg);
+        }
+
+        // Derive the list of entries good to be installed
+        List<FlowEntryInstall> toInstallSafe = new ArrayList<FlowEntryInstall>();
+        for (FlowEntryInstall entry : toInstallList) {
+            // Conflict Check: Verify new entry would not overwrite existing ones
+            if (findMatch(entry.getInstall(), false) != null) {
+                log.warn("Operation Rejected: A flow with same match " + 
+                               "and priority exists on the target node");
+                log.trace("Aborting to install " + entry);
+                continue;
+            }
+            toInstallSafe.add(entry);
+        }
+
+        // Declare failure if all the container flow merged entries clash with existing entries
+        if (toInstallSafe.size() == 0) {
+               String msg = "A flow with same match and priority exists " + 
+                               "on the target node";
+               log.warn(msg);
+            return new Status(StatusCode.CONFLICT, msg);
+        }
+
+        // Try to install an entry at the time
+        Status error = new Status(null, null);
+        boolean oneSucceded = false;
+        for (FlowEntryInstall installEntry : toInstallList) {
+
+            // Install and update database
+               Status ret = addEntriesInternal(installEntry);
+
+            if (ret.isSuccess()) {
+                oneSucceded = true;
+            } else {
+                error = ret;
+                log.warn("Failed to install the entry: " + ret.getDescription());
+            }
+        }
+
+        return (oneSucceded) ? new Status(StatusCode.SUCCESS, null) : error;
+    }
+
+    /**
+     * Given a flow entry and the list of container flows, it returns the list
+     * of container flow merged flow entries good to be installed on this
+     * container. If the list of container flows is null or empty, the install
+     * entry list will contain only one entry, the original flow entry. If the
+     * flow entry is  congruent with all the N container flows, then the output
+     * install entry list will contain N entries. If the output list is empty,
+     * it means the passed flow entry conflicts with all the container flows.
+     *
+     * @param cFlowList The list of container flows
+     * @return the list of container flow merged entries good to be installed on this container
+     */
+    private List<FlowEntryInstall> deriveInstallEntries(FlowEntry request,
+            List<ContainerFlow> cFlowList) {
+        List<FlowEntryInstall> toInstallList = new ArrayList<FlowEntryInstall>(
+                1);
+
+        if (container.getContainerFlows() == null
+                || container.getContainerFlows().isEmpty()) {
+            // No container flows => entry good to be installed unchanged
+            toInstallList.add(new FlowEntryInstall(request.clone(), null));
+        } else {
+            // Create the list of entries to be installed. If the flow entry is
+            // not congruent with any container flow, no install entries will be created
+            for (ContainerFlow cFlow : container.getContainerFlows()) {
+                if (cFlow.allowsFlow(request.getFlow())) {
+                    toInstallList.add(new FlowEntryInstall(request.clone(),
+                            cFlow));
+                }
+            }
+        }
+        return toInstallList;
+    }
+
+    /**
+     * Modify a flow entry with a new one
+     * It runs various validity check and derive the final container flows
+     * merged flow entries to work with
+     *
+     * @param currentFlowEntry
+     * @param newFlowEntry
+     * @return Success or error string
+     */
+    private Status modifyEntry(FlowEntry currentFlowEntry,
+            FlowEntry newFlowEntry) {
+       Status retExt;
+
+        // Sanity checks
+        if (currentFlowEntry == null || currentFlowEntry.getNode() == null
+                || newFlowEntry == null || newFlowEntry.getNode() == null) {
+               String msg ="Modify: Invalid FlowEntry";
+            log.warn(msg + ": {} or {} ", currentFlowEntry, newFlowEntry);
+            return new Status(StatusCode.NOTACCEPTABLE, msg);
+        }
+        if (!currentFlowEntry.getNode().equals(newFlowEntry.getNode())
+                || !currentFlowEntry.getFlowName().equals(
+                        newFlowEntry.getFlowName())) {
+               String msg = "Modify: Incompatible Flow Entries";
+            log.warn(msg +": {} and {}", currentFlowEntry, newFlowEntry);
+            return new Status(StatusCode.NOTACCEPTABLE, msg);
+        }
+
+        // Equality Check
+        if (currentFlowEntry.equals(newFlowEntry)) {
+               String msg = "Modify skipped as flows are the same";
+            log.debug(msg + ": " + currentFlowEntry + " and " + newFlowEntry);
+            return new Status(StatusCode.SUCCESS, msg);
+        }
+
+        // Conflict Check: Verify the new entry would not conflict with another existing one
+        // This is a loose check on the previous original flow entry requests. No check
+        // on the container flow merged flow entries (if any) yet
+        FlowEntryInstall sameMatchOriginalEntry = findMatch(newFlowEntry, true);
+        if (sameMatchOriginalEntry != null
+                && !sameMatchOriginalEntry.getOriginal().equals(
+                        currentFlowEntry)) {
+               String msg = "Operation Rejected: Another flow with same match " +
+                               "and priority exists on the target node";
+            log.warn(msg);
+            return new Status(StatusCode.CONFLICT, msg);
+        }
+
+        // Derive the installed and toInstall entries
+        List<FlowEntryInstall> installedList = deriveInstallEntries(
+                currentFlowEntry.clone(), container.getContainerFlows());
+        List<FlowEntryInstall> toInstallList = deriveInstallEntries(
+                newFlowEntry.clone(), container.getContainerFlows());
+
+        if (toInstallList.isEmpty()) {
+               String msg = "Modify Operation Rejected: The new entry " + 
+                               "conflicts with all the container flows";
+            log.warn(msg);
+            return new Status(StatusCode.CONFLICT, msg);
+        }
+
+        /*
+         * If the two list sizes differ, it means the new flow entry does not
+         * satisfy the same number of container flows the current entry does.
+         * This is only possible when the new entry and current entry have
+         * different match. In this scenario the modification would ultimately
+         * be handled as a remove and add operations in the protocol plugin.
+         *
+         * Also, if any of the new flow entries would clash with an existing
+         * one, we cannot proceed with the modify operation, because it would
+         * fail for some entries and leave stale entries on the network node.
+         * Modify path can be taken only if it can be performed completely,
+         * for all entries.
+         *
+         * So, for the above two cases, to simplify, let's decouple the modify in:
+         * 1) remove current entries
+         * 2) install new entries
+         */
+        boolean decouple = false;
+        if (installedList.size() != toInstallList.size()) {
+            log.info("Modify: New flow entry does not satisfy the same " +
+                    "number of container flows as the original entry does");
+            decouple = true;
+        }
+        List<FlowEntryInstall> toInstallSafe = new ArrayList<FlowEntryInstall>();
+        for (FlowEntryInstall installEntry : toInstallList) {
+            // Conflict Check: Verify the new entry would not overwrite another existing one
+            FlowEntryInstall sameMatchEntry = findMatch(installEntry
+                    .getInstall(), false);
+            if (sameMatchEntry != null
+                    && !sameMatchEntry.getOriginal().equals(currentFlowEntry)) {
+                log.info("Modify: new container flow merged flow entry " + 
+                    "clashes with existing flow");
+                decouple = true;
+            } else {
+                toInstallSafe.add(installEntry);
+            }
+        }
+
+        if (decouple) {
+            // Remove current entries
+            for (FlowEntryInstall currEntry : installedList) {
+                this.removeEntryInternal(currEntry);
+            }
+            // Install new entries
+            for (FlowEntryInstall newEntry : toInstallSafe) {
+                this.addEntriesInternal(newEntry);
+            }
+        } else {
+            /*
+             * The two list have the same size and the entries to install do not
+             * clash with any existing flow on the network node. We assume here
+             * (and might be wrong) that the same container flows that were satisfied
+             * by the current entries are the same that are satisfied by the new
+             * entries. Let's take the risk for now.
+             *
+             * Note: modification has to be complete. If any entry modification
+             * fails, we need to stop, restore the already modified entries,
+             * and declare failure.
+             */
+            Status retModify;
+            int i = 0;
+            int size = toInstallList.size();
+            while (i < size) {
+                // Modify and update database
+                retModify = modifyEntryInternal(installedList.get(i),
+                        toInstallList.get(i));
+                if (retModify.isSuccess()) {
+                    i++;
+                } else {
+                    break;
+                }
+            }
+            // Check if uncompleted modify
+            if (i < size) {
+                log.warn("Unable to perform a complete modify for all " + 
+                               "the container flows merged entries");
+                // Restore original entries
+                int j = 0;
+                while (j < i) {
+                    log.info("Attempting to restore initial entries");
+                    retExt = modifyEntryInternal(toInstallList.get(i),
+                            installedList.get(i));
+                    if (retExt.isSuccess()) {
+                        j++;
+                    } else {
+                        break;
+                    }
+                }
+                // Fatal error, recovery failed
+                if (j < i) {
+                       String msg = "Flow recovery failed ! Unrecoverable Error";
+                    log.error(msg);
+                    return new Status(StatusCode.INTERNALERROR, msg);
+                }
+            }
+        }
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    /**
+     * This is the function that modifies the final container flows merged
+     * entries on the network node and update the database. It expects that
+     * all the  validity checks are passed
+     *
+     * @param currentEntries
+     * @param newEntries
+     * @return
+     */
+    private Status modifyEntryInternal(FlowEntryInstall currentEntries,
+            FlowEntryInstall newEntries) {
+        // Modify the flow on the network node
+        Status status = programmer.modifyFlow(currentEntries.getNode(),
+                currentEntries.getInstall().getFlow(), newEntries.getInstall()
+                        .getFlow());
+
+        if (!status.isSuccess()) {
+            log.warn("SDN Plugin failed to program the flow: " + status.getDescription());
+            return status;
+        }
+
+        log.trace("Modified {} => {}", currentEntries.getInstall(), newEntries
+                .getInstall());
+
+        // Update DB
+        updateLocalDatabase(currentEntries, false);
+        updateLocalDatabase(newEntries, true);
+
+        return status;
+    }
+
+    /**
+     * Remove a flow entry. If the entry is not present in the software view
+     * (entry or node not present), it return successfully
+     *
+     * @param flowEntry
+     * @return
+     */
+    private Status removeEntry(FlowEntry flowEntry) {
+        Status error = new Status(null, null);
+        
+        // Sanity Check
+        if (flowEntry == null || flowEntry.getNode() == null) {
+               String msg = "Invalid FlowEntry";
+            log.warn(msg + ": " + flowEntry);
+            return new Status(StatusCode.NOTACCEPTABLE, msg);
+        }
+
+        // Derive the container flows merged installed entries
+        List<FlowEntryInstall> installedList = deriveInstallEntries(flowEntry
+                .clone(), container.getContainerFlows());
+
+        Set<FlowEntryInstall> flowsOnNode = nodeFlows.get(flowEntry.getNode());
+        boolean atLeastOneRemoved = false;
+        for (FlowEntryInstall entry : installedList) {
+            if (flowsOnNode == null) {
+               String msg = "Removal skipped (Node down)";
+                log.debug(msg + " for flow entry " + flowEntry);
+                return new Status(StatusCode.SUCCESS, msg);
+            }
+            if (!flowsOnNode.contains(entry)) {
+                log.debug("Removal skipped (not present in software view) "
+                        + "for flow entry " + flowEntry);
+
+                if (installedList.size() == 1) {
+                    // If we had only one entry to remove, we are done
+                    return new Status(StatusCode.SUCCESS, null);
+                } else {
+                    continue;
+                }
+            }
+
+            // Remove and update DB
+            Status ret = removeEntryInternal(entry);
+
+            if (!ret.isSuccess()) {
+                error = ret;
+                log.warn("Failed to remove the entry: " + ret.getDescription());
+                if (installedList.size() == 1) {
+                    // If we had only one entry to remove, this is fatal failure
+                    return error;
+                }
+            } else {
+                atLeastOneRemoved = true;
+            }
+        }
+
+        /*
+         * No worries if full removal failed. Consistency checker will
+         * take care of removing the stale entries later, or adjusting
+         * the software database if not in sync with hardware
+         */
+        return (atLeastOneRemoved) ? 
+                       new Status(StatusCode.SUCCESS, null) : error;
+    }
+
+    /**
+     * This is the function that removes the final container flows merged entry
+     * from the network node and update the database. It expects that all the
+     * validity checks are passed
+     *
+     * @param entry the FlowEntryInstall
+     * @return "Success" or error string
+     */
+    private Status removeEntryInternal(FlowEntryInstall entry) {
+        // Mark the entry to be deleted (for CC just in case we fail)
+        entry.toBeDeleted();
+
+        // Remove from node
+        Status status = 
+                       programmer.removeFlow(entry.getNode(), 
+                                       entry.getInstall().getFlow());
+
+        if (!status.isSuccess()) {
+            log.warn("SDN Plugin failed to remove the flow: " + 
+                       status.getDescription());
+            return status;
+        }
+        log.trace("Removed  {}", entry.getInstall());
+
+        // Update DB
+        updateLocalDatabase(entry, false);
+
+        return status;
+    }
+
+    /**
+     * This is the function that installs the final container flow merged entry
+     * on the network node and updates the database. It expects that all the
+     * validity and conflict checks are passed. That means it does not check
+     * whether this flow would conflict or overwrite an existing one.
+     *
+     * @param entry the FlowEntryInstall
+     * @return "Success" or error string
+     */
+    private Status addEntriesInternal(FlowEntryInstall entry) {
+        // Install the flow on the network node
+       Status status = programmer.addFlow(entry.getNode(), 
+                       entry.getInstall().getFlow());
+
+        if (!status.isSuccess()) {
+            log.warn("SDN Plugin failed to program the flow: " + 
+                       status.getDescription());
+            return status;
+        }
+
+        log.trace("Added    {}", entry.getInstall());
+
+        // Update DB
+        updateLocalDatabase(entry, true);
+
+        return status;
+    }
+
+    /**
+     * Returns true if the flow conflicts with all the container's flows.
+     * This means that if the function returns true, the passed flow entry
+     * is congruent with at least one container flow, hence it is good to
+     * be installed on this container.
+     *
+     * @param flowEntry
+     * @return true if flow conflicts with all the container flows, false otherwise
+     */
+    private boolean entryConflictsWithContainerFlows(FlowEntry flowEntry) {
+        List<ContainerFlow> cFlowList = container.getContainerFlows();
+
+        // Validity check and avoid unnecessary computation
+        // Also takes care of default container where no container flows are present
+        if (cFlowList == null || cFlowList.isEmpty()) {
+            return false;
+        }
+
+        for (ContainerFlow cFlow : cFlowList) {
+            if (cFlow.allowsFlow(flowEntry.getFlow())) {
+                // Entry is allowed by at least one container flow: good to go
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private synchronized void updateLocalDatabase(FlowEntryInstall entry,
+            boolean add) {
+        // Update node indexed flow database
+        updateNodeFlowsDB(entry, add);
+
+        // Update group indexed flow database
+        updateGroupFlowsDB(entry, add);
+    }
+
+    /*
+     * Update the node mapped flows database
+     */
+    private void updateNodeFlowsDB(FlowEntryInstall flowEntries, boolean add) {
+        Node node = flowEntries.getNode();
+
+        Set<FlowEntryInstall> flowEntrylist = this.nodeFlows.get(node);
+        if (flowEntrylist == null) {
+            if (add == false) {
+                return;
+            } else {
+                flowEntrylist = new HashSet<FlowEntryInstall>();
+            }
+        }
+
+        if (add == true) {
+            flowEntrylist.add(flowEntries);
+        } else {
+            flowEntrylist.remove(flowEntries);
+        }
+
+        if (flowEntrylist.isEmpty()) {
+            this.nodeFlows.remove(node);
+        } else {
+            this.nodeFlows.put(node, flowEntrylist);
+        }
+    }
+
+    /*
+     * Update the group name mapped flows database
+     */
+    private void updateGroupFlowsDB(FlowEntryInstall flowEntries, boolean add) {
+        Set<FlowEntryInstall> flowList;
+        FlowEntryInstall exists = null;
+        String flowName = flowEntries.getFlowName();
+        String groupName = flowEntries.getGroupName();
+
+        if (this.groupFlows == null) {
+            return;
+        }
+
+        // Flow may not be part of a group
+        if (groupName == null) {
+            return;
+        }
+
+        if (this.groupFlows.containsKey(groupName)) {
+            flowList = this.groupFlows.get(groupName);
+        } else {
+            if (add == false) {
+                return;
+            } else {
+                flowList = new HashSet<FlowEntryInstall>();
+            }
+        }
+
+        for (FlowEntryInstall flow : flowList) {
+            if (flow.equalsByNodeAndName(flowEntries.getNode(), flowName)) {
+                exists = flow;
+                break;
+            }
+        }
+
+        if (exists == null && add == false) {
+            return;
+        }
+
+        if (exists != null) {
+            flowList.remove(exists);
+        }
+
+        if (add == true) {
+            flowList.add(flowEntries);
+        }
+
+        if (flowList.isEmpty()) {
+            this.groupFlows.remove(groupName);
+        } else {
+            this.groupFlows.put(groupName, flowList);
+        }
+    }
+
+    /**
+     * Remove a flow entry that has been added previously First checks if the
+     * entry is effectively present in the local database
+     */
+    @SuppressWarnings("unused")
+    private Status removeEntry(Node node, String flowName) {
+        FlowEntryInstall target = null;
+
+        // Find in database
+        for (FlowEntryInstall entry : this.nodeFlows.get(node)) {
+            if (entry.equalsByNodeAndName(node, flowName)) {
+                target = entry;
+                break;
+            }
+        }
+
+        // If it is not there, stop any further processing
+        if (target == null) {
+            return new Status(StatusCode.SUCCESS, "Entry is not present");
+        }
+
+        // Remove from node
+        Status status = programmer.removeFlow(target.getNode(), target
+                .getInstall().getFlow());
+
+        // Update DB
+        if (status.isSuccess()) {
+            updateLocalDatabase(target, false);
+        }
+
+        return status;
+    }
+
+    @Override
+    public Status installFlowEntry(FlowEntry flowEntry) {
+       Status status;
+        if (inContainerMode) {
+               String msg = "Controller in container mode: Install Refused";
+            status = new Status(StatusCode.NOTACCEPTABLE, msg);
+            log.warn(msg);
+        } else {
+            status = addEntry(flowEntry);
+        }
+        return status;
+    }
+
+    @Override
+    public Status uninstallFlowEntry(FlowEntry entry) {
+       Status status;
+        if (inContainerMode) {
+               String msg = "Controller in container mode: Uninstall Refused";
+            status = new Status(StatusCode.NOTACCEPTABLE, msg);
+            log.warn(msg);
+        } else {
+               status = removeEntry(entry);
+        }
+        return status;
+    }
+
+    @Override
+    public Status modifyFlowEntry(FlowEntry currentFlowEntry,
+            FlowEntry newFlowEntry) {
+       Status status = null;
+        if (inContainerMode) {
+            String msg = "Controller in container mode: Modify Refused";
+            status = new Status(StatusCode.NOTACCEPTABLE, msg);
+            log.warn(msg);
+        } else {
+            status = modifyEntry(currentFlowEntry, newFlowEntry);
+        }
+        return status;
+    }
+
+    @Override
+    public Status modifyOrAddFlowEntry(FlowEntry newFlowEntry) {
+        /*
+         * Run a loose check on the installed entries to decide whether to go
+         * with a add or modify method. A loose check means only check against
+         * the original flow entry requests and not against the installed
+         * flow entries which are the result of the original entry merged with
+         * the container flow(s) (if any). The modifyFlowEntry method in
+         * presence of conflicts with the Container flows (if any) would revert
+         * back to a delete + add pattern
+         */
+        FlowEntryInstall currentFlowEntries = findMatch(newFlowEntry, true);
+
+        if (currentFlowEntries != null) {
+            return modifyFlowEntry(currentFlowEntries.getOriginal(),
+                    newFlowEntry);
+        } else {
+            return installFlowEntry(newFlowEntry);
+        }
+    }
+
+    /**
+     * Try to find in the database if a Flow with the same Match and priority
+     * of the passed one already exists for the specified network node.
+     * Flow, priority and network node are all specified in the FlowEntry
+     * If found, the respective FlowEntryInstall Object is returned
+     *
+     * @param flowEntry the FlowEntry to be tested against the ones installed
+     * @param looseCheck if true, the function will run the check against the
+     *          original flow entry portion of the installed entries
+     * @return null if not found, otherwise the FlowEntryInstall which contains
+     *          the existing flow entry
+     */
+    private FlowEntryInstall findMatch(FlowEntry flowEntry, boolean looseCheck) {
+        Flow flow = flowEntry.getFlow();
+        Match match = flow.getMatch();
+        short priority = flow.getPriority();
+        Set<FlowEntryInstall> thisNodeList = nodeFlows.get(flowEntry.getNode());
+
+        if (thisNodeList != null) {
+            for (FlowEntryInstall flowEntries : thisNodeList) {
+                flow = (looseCheck == false) ? flowEntries.getInstall()
+                        .getFlow() : flowEntries.getOriginal().getFlow();
+                if (flow.getMatch().equals(match)
+                        && flow.getPriority() == priority) {
+                    return flowEntries;
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean checkFlowEntryConflict(FlowEntry flowEntry) {
+        return entryConflictsWithContainerFlows(flowEntry);
+    }
+
+    /**
+     * Updates all installed flows because the container flow got updated
+     * This is obtained in two phases on per node basis:
+     * 1) Uninstall of all flows
+     * 2) Reinstall of all flows
+     * This is needed because a new container flows merged flow may conflict with an existing
+     * old container flows merged flow on the network node
+     */
+    private void updateFlowsContainerFlow() {
+        List<FlowEntryInstall> oldCouples = new ArrayList<FlowEntryInstall>();
+        List<FlowEntry> toReinstall = new ArrayList<FlowEntry>();
+        for (Entry<Node, Set<FlowEntryInstall>> entry : this.nodeFlows
+                .entrySet()) {
+            oldCouples.clear();
+            toReinstall.clear();
+            if (entry.getValue() == null) {
+                continue;
+            }
+            // Create a set of old entries and one of original entries to be reinstalled
+            for (FlowEntryInstall oldCouple : entry.getValue()) {
+                oldCouples.add(oldCouple);
+                toReinstall.add(oldCouple.getOriginal());
+            }
+            // Remove the old couples. No validity checks to be run, use the internal remove
+            for (FlowEntryInstall oldCouple : oldCouples) {
+                this.removeEntryInternal(oldCouple);
+            }
+            // Reinstall the original flow entries, via the regular path: new cFlow merge + validations
+            for (FlowEntry flowEntry : toReinstall) {
+                this.installFlowEntry(flowEntry);
+            }
+        }
+    }
+
+    public void nonClusterObjectCreate() {
+        nodeFlows = new ConcurrentHashMap<Node, Set<FlowEntryInstall>>();
+        TSPolicies = new ConcurrentHashMap<String, Object>();
+        groupFlows = new ConcurrentHashMap<String, Set<FlowEntryInstall>>();
+        staticFlowsOrdinal = new ConcurrentHashMap<Integer, Integer>();
+        portGroupConfigs = new ConcurrentHashMap<String, PortGroupConfig>();
+        portGroupData = new ConcurrentHashMap<PortGroupConfig, Map<Node, PortGroup>>();
+        staticFlows = new ConcurrentHashMap<Integer, FlowConfig>();
+        flowsSaveEvent = new HashMap<Long, String>();
+        inactiveFlows = new ArrayList<FlowEntry>(1);
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    @Override
+    public void setTSPolicyData(String policyname, Object o, boolean add) {
+
+        if (add) {
+            /* Check if this policy already exists */
+            if (!(TSPolicies.containsKey(policyname))) {
+                TSPolicies.put(policyname, o);
+            }
+        } else {
+            TSPolicies.remove(policyname);
+        }
+        if (frmAware != null) {
+            synchronized (frmAware) {
+                for (IForwardingRulesManagerAware frma : frmAware) {
+                    try {
+                        frma.policyUpdate(policyname, add);
+                    } catch (Exception e) {
+                        log.error("Exception on callback", e);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public Map<String, Object> getTSPolicyData() {
+        return TSPolicies;
+    }
+
+    @Override
+    public Object getTSPolicyData(String policyName) {
+        if (TSPolicies.containsKey(policyName)) {
+            return TSPolicies.get(policyName);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public List<FlowEntry> getFlowEntriesForGroup(String policyName) {
+        List<FlowEntry> list = null;
+        if (this.groupFlows != null && this.groupFlows.containsKey(policyName)) {
+            list = new ArrayList<FlowEntry>();
+            for (FlowEntryInstall entries : groupFlows.get(policyName)) {
+                list.add(entries.getOriginal());
+            }
+            return new ArrayList<FlowEntry>();
+        }
+        return list;
+    }
+
+    @Override
+    public void addOutputPort(Node node, String flowName,
+            List<NodeConnector> portList) {
+
+        Set<FlowEntryInstall> flowEntryList = this.nodeFlows.get(node);
+
+        for (FlowEntryInstall flow : flowEntryList) {
+            if (flow.getFlowName().equals(flowName)) {
+                FlowEntry currentFlowEntry = flow.getOriginal();
+                FlowEntry newFlowEntry = currentFlowEntry.clone();
+                for (NodeConnector dstPort : portList) {
+                    newFlowEntry.getFlow().addAction(new Output(dstPort));
+                }
+                Status error = modifyEntry(currentFlowEntry, newFlowEntry);
+                if (error.isSuccess()) {
+                    log.info("Ports {} added to FlowEntry {}", portList,
+                            flowName);
+                } else {
+                    log.warn("Failed to add ports {} to Flow entry {}: "
+                            + error.getDescription(), portList, 
+                            currentFlowEntry.toString());
+                }
+                return;
+            }
+        }
+        log.warn("Failed to add ports to Flow {} on Node {}: Entry Not Found",
+                flowName, node);
+    }
+
+    @Override
+    public void removeOutputPort(Node node, String flowName,
+            List<NodeConnector> portList) {
+
+        Set<FlowEntryInstall> flowEntryList = this.nodeFlows.get(node);
+
+        for (FlowEntryInstall flow : flowEntryList) {
+            if (flow.getFlowName().equals(flowName)) {
+                FlowEntry currentFlowEntry = flow.getOriginal();
+                FlowEntry newFlowEntry = currentFlowEntry.clone();
+                for (NodeConnector dstPort : portList) {
+                    Action action = new Output(dstPort);
+                    newFlowEntry.getFlow().removeAction(action);
+                }
+                Status status = modifyEntry(currentFlowEntry, newFlowEntry);
+                if (status.isSuccess()) {
+                    log.info("Ports {} removed from FlowEntry {}", portList,
+                            flowName);
+                } else {
+                    log.warn("Failed to remove ports {} from Flow entry {}: "
+                            + status.getDescription(), portList, 
+                            currentFlowEntry.toString());
+                }
+                return;
+            }
+        }
+        log
+                .warn(
+                        "Failed to remove ports from Flow {} on Node {}: Entry Not Found",
+                        flowName, node);
+    }
+
+    /*
+     * This function assumes the target flow has only one output port
+     */
+    @Override
+    public void replaceOutputPort(Node node, String flowName,
+            NodeConnector outPort) {
+        FlowEntry currentFlowEntry = null;
+        FlowEntry newFlowEntry = null;
+        Set<FlowEntryInstall> flowEntryList = this.nodeFlows.get(node);
+
+        // Find the flow
+        for (FlowEntryInstall flow : flowEntryList) {
+            if (flow.getFlowName().equals(flowName)) {
+                currentFlowEntry = flow.getOriginal();
+                break;
+            }
+        }
+        if (currentFlowEntry == null) {
+            log
+                    .warn(
+                            "Failed to replace output port for flow {} on node {}: Entry Not Found",
+                            flowName, node);
+            return;
+        }
+
+        // Create a flow copy with the new output port
+        newFlowEntry = currentFlowEntry.clone();
+        Action target = null;
+        for (Action action : newFlowEntry.getFlow().getActions()) {
+            if (action.getType() == ActionType.OUTPUT) {
+                target = action;
+                break;
+            }
+        }
+        newFlowEntry.getFlow().removeAction(target);
+        newFlowEntry.getFlow().addAction(new Output(outPort));
+
+        // Modify on network node
+        Status status = modifyEntry(currentFlowEntry, newFlowEntry);
+
+        if (status.isSuccess()) {
+            log.info("Output port replaced with " + outPort
+                    + " for flow {} on node {}", flowName, node);
+        } else {
+            log.warn("Failed to replace output port for flow {} on node {}: ",
+                    status.getDescription(), flowName, node);
+        }
+        return;
+    }
+
+    @Override
+    public NodeConnector getOutputPort(Node node, String flowName) {
+        Set<FlowEntryInstall> flowEntryList = this.nodeFlows.get(node);
+
+        for (FlowEntryInstall flow : flowEntryList) {
+            if (flow.getFlowName().equals(flowName)) {
+                for (Action action : flow.getOriginal().getFlow().getActions()) {
+                    if (action.getType() == ActionType.OUTPUT) {
+                        return ((Output) action).getPort();
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private void cacheStartup() {
+        allocateCaches();
+        retrieveCaches();
+    }
+
+    @SuppressWarnings("deprecation")
+    private void allocateCaches() {
+        if (this.clusterContainerService == null) {
+            log
+                    .warn("Un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+
+        log.debug("FRM allocateCaches for Container {}", container);
+
+        try {
+            clusterContainerService.createCache("frm.nodeFlows", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.groupFlows", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.staticFlows", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.flowsSaveEvent", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.staticFlowsOrdinal",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.portGroupConfigs", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.portGroupData", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterContainerService.createCache("frm.TSPolicies", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+        } catch (CacheConfigException cce) {
+            log.error("FRM CacheConfigException");
+        } catch (CacheExistException cce) {
+            log.error("FRM CacheExistException");
+        }
+    }
+
+    @SuppressWarnings( { "unchecked", "deprecation" })
+    private void retrieveCaches() {
+        ConcurrentMap<?, ?> map;
+
+        if (this.clusterContainerService == null) {
+            log
+                    .warn("un-initialized clusterContainerService, can't retrieve cache");
+            return;
+        }
+
+        log.debug("FRM retrieveCaches for Container {}", container);
+
+        map = clusterContainerService.getCache("frm.nodeFlows");
+        if (map != null) {
+            nodeFlows = (ConcurrentMap<Node, Set<FlowEntryInstall>>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.nodeFlows allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.groupFlows");
+        if (map != null) {
+            groupFlows = (ConcurrentMap<String, Set<FlowEntryInstall>>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.groupFlows allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.staticFlows");
+        if (map != null) {
+            staticFlows = (ConcurrentMap<Integer, FlowConfig>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.staticFlows allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.flowsSaveEvent");
+        if (map != null) {
+            flowsSaveEvent = (ConcurrentMap<Long, String>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.flowsSaveEvent allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.staticFlowsOrdinal");
+        if (map != null) {
+            staticFlowsOrdinal = (ConcurrentMap<Integer, Integer>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.staticFlowsOrdinal allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.portGroupConfigs");
+        if (map != null) {
+            portGroupConfigs = (ConcurrentMap<String, PortGroupConfig>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.portGroupConfigs allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.portGroupData");
+        if (map != null) {
+            portGroupData = (ConcurrentMap<PortGroupConfig, Map<Node, PortGroup>>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.portGroupData allocation failed for Container {}",
+                            container);
+        }
+
+        map = clusterContainerService.getCache("frm.TSPolicies");
+        if (map != null) {
+            TSPolicies = (ConcurrentMap<String, Object>) map;
+        } else {
+            log
+                    .error(
+                            "FRM Cache frm.TSPolicies allocation failed for Container {}",
+                            container);
+        }
+
+    }
+
+    @SuppressWarnings("deprecation")
+       private void destroyCaches() {
+        if (this.clusterContainerService == null) {
+            log
+                    .warn("Un-initialized clusterContainerService, can't destroy cache");
+            return;
+        }
+
+        log.debug("FRM destroyCaches for Container {}", container);
+        clusterContainerService.destroyCache("frm.nodeFlows");
+        clusterContainerService.destroyCache("frm.TSPolicies");
+        clusterContainerService.destroyCache("frm.groupFlows");
+        clusterContainerService.destroyCache("frm.staticFlows");
+        clusterContainerService.destroyCache("frm.flowsSaveEvent");
+        clusterContainerService.destroyCache("frm.staticFlowsOrdinal");
+        clusterContainerService.destroyCache("frm.portGroupData");
+        clusterContainerService.destroyCache("frm.portGroupConfigs");
+        nonClusterObjectCreate();
+    }
+
+    private boolean flowConfigExists(FlowConfig config) {
+        // As per customer requirement, flow name has to be unique on per node
+        // id basis
+        for (FlowConfig fc : staticFlows.values()) {
+            if (fc.isByNameAndNodeIdEqual(config)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Status addStaticFlow(FlowConfig config, boolean restore) {
+        StringBuffer resultStr = new StringBuffer();
+        boolean multipleFlowPush = false;
+        String error;
+        Status status;
+        config.setStatus(StatusCode.SUCCESS.toString());
+
+        // Presence check
+        if (flowConfigExists(config)) {
+            error = "Entry with this name on specified switch already exists";
+            config.setStatus(error);
+            return new Status(StatusCode.CONFLICT, error);
+        }
+
+        // Skip validation check if we are trying to restore a saved config
+        if (!restore && !config.isValid(container, resultStr)) {
+            log.debug(resultStr.toString());
+            error = "Invalid Configuration (" + resultStr.toString() + ")";
+            config.setStatus(error);
+            return new Status(StatusCode.BADREQUEST, error);
+        }
+
+        if ((config.getIngressPort() == null) && config.getPortGroup() != null) {
+            for (String portGroupName : portGroupConfigs.keySet()) {
+                if (portGroupName.equalsIgnoreCase(config.getPortGroup())) {
+                    multipleFlowPush = true;
+                    break;
+                }
+            }
+            if (!multipleFlowPush) {
+                log.debug(resultStr.toString());
+                error = "Invalid Configuration (Invalid PortGroup Name)";
+                config.setStatus(error);
+                return new Status(StatusCode.BADREQUEST, error);
+            }
+        }
+
+        /*
+         * If requested program the entry in hardware first before updating the
+         * StaticFlow DB
+         */
+        if (!multipleFlowPush) {
+            // Program hw
+            if (config.installInHw()) {
+                FlowEntry entry = config.getFlowEntry();
+                status = this.addEntry(entry);
+                if (!status.isSuccess()) {
+                    config.setStatus(status.getDescription());
+                    if (!restore) {
+                        return status;
+                    }
+                }
+            }
+        }
+
+        /*
+         * When the control reaches this point, either of the following
+         * conditions is true 1. This is a single entry configuration (non
+         * PortGroup) and the hardware installation is successful 2. This is a
+         * multiple entry configuration (PortGroup) and hardware installation is
+         * NOT done directly on this event. 3. The User prefers to retain the
+         * configuration in Controller and skip hardware installation.
+         *
+         * Hence it is safe to update the StaticFlow DB at this point.
+         *
+         * Note : For the case of PortGrouping, it is essential to have this DB
+         * populated before the PortGroupListeners can query for the DB
+         * triggered using portGroupChanged event...
+         */
+        Integer ordinal = staticFlowsOrdinal.get(0);
+        staticFlowsOrdinal.put(0, ++ordinal);
+        staticFlows.put(ordinal, config);
+
+        if (multipleFlowPush) {
+            PortGroupConfig pgconfig = portGroupConfigs.get(config
+                    .getPortGroup());
+            Map<Node, PortGroup> existingData = portGroupData.get(pgconfig);
+            if (existingData != null) {
+                portGroupChanged(pgconfig, existingData, true);
+            }
+        }
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private void addStaticFlowsToSwitch(Node node) {
+        for (FlowConfig config : staticFlows.values()) {
+            if (config.isPortGroupEnabled()) {
+                continue;
+            }
+            if (config.getNode().equals(node)) {
+                if (config.installInHw()
+                        && !config.getStatus().equals(
+                                       StatusCode.SUCCESS.toString())) {
+                    Status status = this.addEntry(config.getFlowEntry());
+                    config.setStatus(status.getDescription());
+                }
+            }
+        }
+    }
+
+    private void updateStaticFlowConfigsOnNodeDown(Node node) {
+        log.trace("Updating Static Flow configs on node down: " + node);
+
+        for (FlowConfig config : staticFlows.values()) {
+            if (config.isPortGroupEnabled()) {
+                continue;
+            }
+            if (config.installInHw() && config.getNode().equals(node)) {
+                config.setStatus("Node is down");
+            }
+        }
+    }
+
+    private void updateStaticFlowConfigsOnContainerModeChange(UpdateType update) {
+        log.trace("Updating Static Flow configs on container mode change: "
+                + update);
+
+        for (FlowConfig config : staticFlows.values()) {
+            if (config.isPortGroupEnabled()) {
+                continue;
+            }
+            if (config.installInHw()) {
+                switch (update) {
+                case ADDED:
+                    config
+                            .setStatus("Removed from node because in container mode");
+                    break;
+                case REMOVED:
+                    config.setStatus(StatusCode.SUCCESS.toString());
+                    break;
+                default:
+                }
+            }
+        }
+    }
+
+    public Status removeStaticFlow(FlowConfig config) {
+        /*
+         * No config.isInternal() check as NB does not take this path and GUI
+         * cannot issue a delete on an internal generated flow. We need this path
+         * to be accessible when switch mode is changed from proactive to
+         * reactive, so that we can remove the internal generated LLDP and ARP
+         * punt flows
+         */
+        for (Map.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
+            if (entry.getValue().isByNameAndNodeIdEqual(config)) {
+                // Program the network node
+               Status status = this.removeEntry(config.getFlowEntry());
+                // Update configuration database if programming was successful
+                if (status.isSuccess()) {
+                    staticFlows.remove(entry.getKey());
+                    return status;
+                } else {
+                    entry.getValue().setStatus(status.getDescription());
+                    return status;
+                }
+            }
+        }
+        return new Status(StatusCode.NOTFOUND, "Entry Not Present");
+    }
+
+    @Override
+    public Status removeStaticFlow(String name, Node node) {
+        for (Map.Entry<Integer, FlowConfig> mapEntry : staticFlows.entrySet()) {
+            FlowConfig entry = mapEntry.getValue();
+            Status status = new Status(null,null);
+            if (entry.isByNameAndNodeIdEqual(name, node)) {
+                // Validity check for api3 entry point
+                if (entry.isInternalFlow()) {
+                       String msg = "Invalid operation: Controller generated " +
+                                       "flow cannot be deleted";
+                       log.warn(msg);
+                       return new Status(StatusCode.NOTACCEPTABLE, msg);
+                }
+                if (!entry.isPortGroupEnabled()) {
+                    // Program the network node
+                    status = this.removeEntry(entry.getFlowEntry());
+                }
+                // Update configuration database if programming was successful
+                if (status.isSuccess()) {
+                    staticFlows.remove(mapEntry.getKey());
+                    return status;
+                } else {
+                    entry.setStatus(status.getDescription());
+                    return status;
+                }
+            }
+        }
+        return new Status(StatusCode.NOTFOUND, "Entry Not Present");
+    }
+
+    public Status modifyStaticFlow(FlowConfig newFlowConfig) {
+        // Validity check for api3 entry point
+        if (newFlowConfig.isInternalFlow()) {
+               String msg = "Invalid operation: Controller generated flow " + 
+                                       "cannot be modified";
+               log.warn(msg);
+            return new Status(StatusCode.NOTACCEPTABLE, msg);
+        }
+
+        // Validity Check
+        StringBuffer resultStr = new StringBuffer();
+        if (!newFlowConfig.isValid(container, resultStr)) {
+            String msg = "Invalid Configuration (" + resultStr.toString()
+                    + ")";
+            newFlowConfig.setStatus(msg);
+            log.warn(msg);
+            return new Status(StatusCode.BADREQUEST, msg);
+        }
+
+        FlowConfig oldFlowConfig = null;
+        Integer index = null;
+        for (Map.Entry<Integer, FlowConfig> mapEntry : staticFlows.entrySet()) {
+            FlowConfig entry = mapEntry.getValue();
+            if (entry.isByNameAndNodeIdEqual(newFlowConfig.getName(),
+                    newFlowConfig.getNode())) {
+                oldFlowConfig = entry;
+                index = mapEntry.getKey();
+                break;
+            }
+        }
+
+        if (oldFlowConfig == null) {
+               String msg = "Attempt to modify a non existing static flow";
+               log.warn(msg);
+               return new Status(StatusCode.NOTFOUND, msg);
+        }
+
+        // Do not attempt to reinstall the flow, warn user
+        if (newFlowConfig.equals(oldFlowConfig)) {
+               String msg = "No modification detected";
+               log.info("Static flow modification skipped: " + msg);
+            return new Status(StatusCode.SUCCESS, msg);
+        }
+
+        // If flow is installed, program the network node
+        Status status = new Status(StatusCode.SUCCESS, "Saved in config");
+        if (oldFlowConfig.installInHw()) {
+               status = this.modifyEntry(oldFlowConfig.getFlowEntry(),
+                    newFlowConfig.getFlowEntry());
+        }
+
+        // Update configuration database if programming was successful
+        if (status.isSuccess()) {
+            newFlowConfig.setStatus(status.getDescription());
+            staticFlows.put(index, newFlowConfig);
+        }
+
+        return status;
+    }
+
+    @Override
+    public Status toggleStaticFlowStatus(FlowConfig config) {
+        // Validity check for api3 entry point
+        if (config.isInternalFlow()) {
+               String msg = "Invalid operation: Controller generated flow " +
+                               "cannot be modified";
+               log.warn(msg);
+               return new Status(StatusCode.NOTACCEPTABLE, msg);
+        }
+
+        for (Map.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
+            FlowConfig conf = entry.getValue();
+            if (conf.isByNameAndNodeIdEqual(config)) {
+                // Program the network node
+                Status status = new Status(StatusCode.SUCCESS, null);
+                if (conf.installInHw()) {
+                    status = this.removeEntry(conf.getFlowEntry());
+                } else {
+                    status = this.addEntry(conf.getFlowEntry());
+                }
+                if (!status.isSuccess()) {
+                    conf.setStatus(status.getDescription());
+                    return status;
+                }
+
+                // Update Configuration database
+                conf.setStatus(StatusCode.SUCCESS.toString());
+                conf.toggleStatus();
+                return status;
+            }
+        }
+        return new Status(StatusCode.NOTFOUND,
+                       "Unable to locate the entry. Failed to toggle status");
+    }
+
+    /**
+     * Uninstall all the Flow Entries present in the software view
+     * A copy of each entry is stored in the inactive list so
+     * that it can be re-applied when needed
+     * This function is called on the default container instance of FRM only
+     * when the first container is created
+     */
+    private void uninstallAllFlowEntries() {
+        log.info("Uninstalling all flows");
+
+        // Store entries / create target list
+        for (ConcurrentMap.Entry<Node, Set<FlowEntryInstall>> mapEntry : nodeFlows
+                .entrySet()) {
+            for (FlowEntryInstall flowEntries : mapEntry.getValue()) {
+                inactiveFlows.add(flowEntries.getOriginal());
+            }
+        }
+
+        // Now remove the entries
+        for (FlowEntry flowEntry : inactiveFlows) {
+            Status status = this.removeEntry(flowEntry);
+            if (!status.isSuccess()) {
+                log.warn("Failed to remove entry: {}: " + 
+                               status.getDescription(), flowEntry);
+            }
+        }
+    }
+
+    /**
+     * Re-install all the Flow Entries present in the inactive list
+     * The inactive list will be empty at the end of this call
+     * This function is called on the default container instance of FRM only
+     * when the last container is deleted
+     */
+    private void reinstallAllFlowEntries() {
+        log.info("Reinstalling all inactive flows");
+
+        for (FlowEntry flowEntry : this.inactiveFlows) {
+               Status status = this.addEntry(flowEntry);
+            if (!status.isSuccess()) {
+                log.warn("Failed to install entry: {}: " + 
+                               status.getDescription(), flowEntry);
+            }
+        }
+
+        // Empty inactive list in any case
+        inactiveFlows.clear();
+    }
+
+    public List<FlowConfig> getStaticFlows() {
+        return getStaticFlowsOrderedList(staticFlows, staticFlowsOrdinal.get(0)
+                .intValue());
+    }
+
+    // TODO: need to come out with a better algorithm for mantaining the order
+    // of the configuration entries
+    // with actual one, index associated to deleted entries cannot be reused and
+    // map grows...
+    private List<FlowConfig> getStaticFlowsOrderedList(
+            ConcurrentMap<Integer, FlowConfig> flowMap, int maxKey) {
+        List<FlowConfig> orderedList = new ArrayList<FlowConfig>();
+        for (int i = 0; i <= maxKey; i++) {
+            FlowConfig entry = flowMap.get(i);
+            if (entry != null) {
+                orderedList.add(entry);
+            }
+        }
+        return orderedList;
+    }
+
+    @Override
+    public FlowConfig getStaticFlow(String name, Node node) {
+        for (FlowConfig config : staticFlows.values()) {
+            if (config.isByNameAndNodeIdEqual(name, node)) {
+                return config;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<FlowConfig> getStaticFlows(Node node) {
+        List<FlowConfig> list = new ArrayList<FlowConfig>();
+        for (FlowConfig config : staticFlows.values()) {
+            if (config.onNode(node)) {
+                list.add(config);
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public List<String> getStaticFlowNamesForNode(Node node) {
+        List<String> list = new ArrayList<String>();
+        for (FlowConfig config : staticFlows.values()) {
+            if (config.onNode(node)) {
+                list.add(config.getName());
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public List<Node> getListNodeWithConfiguredFlows() {
+        Set<Node> set = new HashSet<Node>();
+        for (FlowConfig config : staticFlows.values()) {
+            set.add(config.getNode());
+        }
+        return new ArrayList<Node>(set);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadFlowConfiguration() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<Integer, FlowConfig> confList = (ConcurrentMap<Integer, FlowConfig>) objReader
+                .read(this, frmFileName);
+
+        ConcurrentMap<String, PortGroupConfig> pgConfig = (ConcurrentMap<String, PortGroupConfig>) objReader
+                .read(this, portGroupFileName);
+
+        if (pgConfig != null) {
+            for (Map.Entry<String, PortGroupConfig> entry : pgConfig.entrySet()) {
+                addPortGroupConfig(entry.getKey(), entry.getValue()
+                        .getMatchString(), true);
+            }
+        }
+
+        if (confList == null) {
+            return;
+        }
+
+        int maxKey = 0;
+        for (Integer key : confList.keySet()) {
+            if (key.intValue() > maxKey)
+                maxKey = key.intValue();
+        }
+
+        for (FlowConfig conf : getStaticFlowsOrderedList(confList, maxKey)) {
+            addStaticFlow(conf, true);
+        }
+    }
+
+    @Override
+    public Object readObject(ObjectInputStream ois)
+            throws FileNotFoundException, IOException, ClassNotFoundException {
+        return ois.readObject();
+    }
+
+    public Status saveConfig() {
+        // Publish the save config event to the cluster nodes
+        flowsSaveEvent.put(new Date().getTime(), SAVE);
+        return saveConfigInternal();
+    }
+
+    private Status saveConfigInternal() {
+        ObjectWriter objWriter = new ObjectWriter();
+        ConcurrentHashMap<Integer, FlowConfig> nonDynamicFlows = new ConcurrentHashMap<Integer, FlowConfig>();
+        for (Integer ordinal : staticFlows.keySet()) {
+            FlowConfig config = staticFlows.get(ordinal);
+            if (config.isDynamic())
+                continue;
+            nonDynamicFlows.put(ordinal, config);
+        }
+        objWriter.write(nonDynamicFlows, frmFileName);
+        objWriter.write(new ConcurrentHashMap<String, PortGroupConfig>(
+                portGroupConfigs), portGroupFileName);
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public void entryCreated(Long key, String cacheName, boolean local) {
+    }
+
+    @Override
+    public void entryUpdated(Long key, String new_value, String cacheName,
+            boolean originLocal) {
+        saveConfigInternal();
+    }
+
+    @Override
+    public void entryDeleted(Long key, String cacheName, boolean originLocal) {
+    }
+
+    @Override
+    public void subnetNotify(Subnet sub, boolean add) {
+    }
+
+    private void installImplicitARPReplyPunt(Node node) {
+
+        if (node == null) {
+            return;
+        }
+
+        List<String> puntAction = new ArrayList<String>();
+        puntAction.add(ActionType.CONTROLLER.toString());
+
+        FlowConfig allowARP = new FlowConfig();
+        allowARP.setInstallInHw(true);
+        allowARP.setName("**Punt ARP Reply");
+        allowARP.setPriority("500");
+        allowARP.setNode(node);
+        allowARP.setEtherType("0x"
+                + Integer.toHexString(EtherTypes.ARP.intValue()).toUpperCase());
+        allowARP.setDstMac(HexEncode.bytesToHexString(switchManager
+                .getControllerMAC()));
+        allowARP.setActions(puntAction);
+        addStaticFlow(allowARP, false);
+    }
+
+    @Override
+    public void modeChangeNotify(Node node, boolean proactive) {
+        List<FlowConfig> defaultConfigs = new ArrayList<FlowConfig>();
+
+        List<String> puntAction = new ArrayList<String>();
+        puntAction.add(ActionType.CONTROLLER.toString());
+
+        FlowConfig allowARP = new FlowConfig();
+        allowARP.setInstallInHw(true);
+        allowARP.setName("**Punt ARP");
+        allowARP.setPriority("1");
+        allowARP.setNode(node);
+        allowARP.setEtherType("0x"
+                + Integer.toHexString(EtherTypes.ARP.intValue()).toUpperCase());
+        allowARP.setActions(puntAction);
+        defaultConfigs.add(allowARP);
+
+        FlowConfig allowLLDP = new FlowConfig();
+        allowLLDP.setInstallInHw(true);
+        allowLLDP.setName("**Punt LLDP");
+        allowLLDP.setPriority("1");
+        allowLLDP.setNode(node);
+        allowLLDP.setEtherType("0x"
+                               + Integer.toHexString(EtherTypes.LLDP.intValue())
+                                .toUpperCase());
+        allowLLDP.setActions(puntAction);
+        defaultConfigs.add(allowLLDP);
+
+        List<String> dropAction = new ArrayList<String>();
+        dropAction.add(ActionType.DROP.toString());
+
+        FlowConfig dropAllConfig = new FlowConfig();
+        dropAllConfig.setInstallInHw(true);
+        dropAllConfig.setName("**Catch-All Drop");
+        dropAllConfig.setPriority("0");
+        dropAllConfig.setNode(node);
+        dropAllConfig.setActions(dropAction);
+        defaultConfigs.add(dropAllConfig);
+
+        for (FlowConfig fc : defaultConfigs) {
+            if (proactive) {
+                addStaticFlow(fc, false);
+            } else {
+                removeStaticFlow(fc);
+            }
+        }
+
+        log.info("Set Switch {} Mode to {}", node, proactive);
+    }
+
+    /**
+     * Remove from the databases all the flows installed on the node
+     *
+     * @param node
+     */
+    private synchronized void cleanDatabaseForNode(Node node) {
+        log.info("Cleaning Flow database for Node " + node.toString());
+
+        // Find out which groups the node's flows are part of
+        Set<String> affectedGroups = new HashSet<String>();
+        Set<FlowEntryInstall> flowEntryList = nodeFlows.get(node);
+        if (flowEntryList != null) {
+            for (FlowEntryInstall entry : flowEntryList) {
+                String groupName = entry.getGroupName();
+                if (groupName != null) {
+                    affectedGroups.add(groupName);
+                }
+            }
+        }
+
+        // Remove the node's flows from the group indexed flow database
+        if (!affectedGroups.isEmpty()) {
+            for (String group : affectedGroups) {
+                Set<FlowEntryInstall> flowList = groupFlows.get(group);
+                Set<FlowEntryInstall> toRemove = new HashSet<FlowEntryInstall>();
+                for (FlowEntryInstall entry : flowList) {
+                    if (node.equals(entry.getNode())) {
+                        toRemove.add(entry);
+                    }
+                }
+                flowList.removeAll(toRemove);
+                if (flowList.isEmpty()) {
+                    groupFlows.remove(group);
+                }
+            }
+        }
+
+        // Remove the node's flows from the node indexed flow database
+        nodeFlows.remove(node);
+    }
+
+    @Override
+    public void notifyNode(Node node, UpdateType type,
+            Map<String, Property> propMap) {
+        switch (type) {
+        case ADDED:
+            addStaticFlowsToSwitch(node);
+            break;
+        case REMOVED:
+            cleanDatabaseForNode(node);
+            updateStaticFlowConfigsOnNodeDown(node);
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void notifyNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Map<String, Property> propMap) {
+    }
+
+    private FlowConfig getDerivedFlowConfig(FlowConfig original,
+            String configName, Short port) {
+        FlowConfig derivedFlow = new FlowConfig(original);
+        derivedFlow.setDynamic(true);
+        derivedFlow.setPortGroup(null);
+        derivedFlow.setName(original.getName() + "_" + configName + "_" + port);
+        derivedFlow.setIngressPort(port + "");
+        return derivedFlow;
+    }
+
+    private void addPortGroupFlows(PortGroupConfig config, Node node,
+            PortGroup data) {
+        for (Iterator<FlowConfig> it = staticFlows.values().iterator(); it
+                .hasNext();) {
+            FlowConfig staticFlow = it.next();
+            if (staticFlow.getPortGroup() == null) {
+                continue;
+            }
+            if ((staticFlow.getNode().equals(node))
+                    && (staticFlow.getPortGroup().equals(config.getName()))) {
+                for (Short port : data.getPorts()) {
+                    FlowConfig derivedFlow = getDerivedFlowConfig(staticFlow,
+                            config.getName(), port);
+                    addStaticFlow(derivedFlow, false);
+                }
+            }
+        }
+    }
+
+    private void removePortGroupFlows(PortGroupConfig config, Node node,
+            PortGroup data) {
+        for (Iterator<FlowConfig> it = staticFlows.values().iterator(); it
+                .hasNext();) {
+            FlowConfig staticFlow = it.next();
+            if (staticFlow.getPortGroup() == null) {
+                continue;
+            }
+            if ((staticFlow.getNode().equals(node))
+                    && (staticFlow.getPortGroup().equals(config.getName()))) {
+                for (Short port : data.getPorts()) {
+                    FlowConfig derivedFlow = getDerivedFlowConfig(staticFlow,
+                            config.getName(), port);
+                    removeStaticFlow(derivedFlow);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void portGroupChanged(PortGroupConfig config,
+            Map<Node, PortGroup> data, boolean add) {
+        log.info("PortGroup Changed for :" + config + " Data: "
+                 + portGroupData);
+        Map<Node, PortGroup> existingData = portGroupData.get(config);
+        if (existingData != null) {
+            for (Map.Entry<Node, PortGroup> entry : data.entrySet()) {
+                PortGroup existingPortGroup = existingData.get(entry.getKey());
+                if (existingPortGroup == null) {
+                    if (add) {
+                        existingData.put(entry.getKey(), entry.getValue());
+                        addPortGroupFlows(config, entry.getKey(), entry
+                                .getValue());
+                    }
+                } else {
+                    if (add) {
+                        existingPortGroup.getPorts().addAll(
+                                entry.getValue().getPorts());
+                        addPortGroupFlows(config, entry.getKey(), entry
+                                .getValue());
+                    } else {
+                        existingPortGroup.getPorts().removeAll(
+                                entry.getValue().getPorts());
+                        removePortGroupFlows(config, entry.getKey(), entry
+                                .getValue());
+                    }
+                }
+            }
+        } else {
+            if (add) {
+                portGroupData.put(config, data);
+                for (Node swid : data.keySet()) {
+                    addPortGroupFlows(config, swid, data.get(swid));
+                }
+            }
+        }
+    }
+
+    public boolean addPortGroupConfig(String name, String regex, boolean restore) {
+        PortGroupConfig config = portGroupConfigs.get(name);
+        if (config != null)
+            return false;
+
+        if ((portGroupProvider == null) && !restore) {
+            return false;
+        }
+        if ((portGroupProvider != null)
+                && (!portGroupProvider.isMatchCriteriaSupported(regex))) {
+            return false;
+        }
+
+        config = new PortGroupConfig(name, regex);
+        portGroupConfigs.put(name, config);
+        if (portGroupProvider != null) {
+            portGroupProvider.createPortGroupConfig(config);
+        }
+        return true;
+    }
+
+    public boolean delPortGroupConfig(String name) {
+        PortGroupConfig config = portGroupConfigs.get(name);
+        if (config == null) {
+            return false;
+        }
+
+        if (portGroupProvider != null) {
+            portGroupProvider.deletePortGroupConfig(config);
+        }
+        portGroupConfigs.remove(name);
+        return true;
+    }
+
+    private void usePortGroupConfig(String name) {
+        PortGroupConfig config = portGroupConfigs.get(name);
+        if (config == null) {
+            return;
+        }
+        if (portGroupProvider != null) {
+            Map<Node, PortGroup> data = portGroupProvider
+                    .getPortGroupData(config);
+            portGroupData.put(config, data);
+        }
+    }
+
+    @Override
+    public Map<String, PortGroupConfig> getPortGroupConfigs() {
+        return portGroupConfigs;
+    }
+
+    public boolean isPortGroupSupported() {
+        if (portGroupProvider == null) {
+            return false;
+        }
+        return true;
+    }
+
+    // Fir PortGroupProvider to use regular Dependency Manager
+    /* @SuppressWarnings("rawtypes") */
+    /* public void bind(Object arg0, Map arg1) throws Exception { */
+    /* if (arg0 instanceof PortGroupProvider) { */
+    /* setPortGroupProvider((PortGroupProvider)arg0); */
+    /* } */
+    /* } */
+
+    /* @SuppressWarnings("rawtypes") */
+    /* @Override */
+    /* public void unbind(Object arg0, Map arg1) throws Exception { */
+    /* if (arg0 instanceof PortGroupProvider) { */
+    /* portGroupProvider = null; */
+    /* } */
+    /* } */
+
+    public void setIContainer(IContainer s) {
+        this.container = s;
+    }
+
+    public void unsetIContainer(IContainer s) {
+        if (this.container == s) {
+            this.container = null;
+        }
+    }
+
+    public PortGroupProvider getPortGroupProvider() {
+        return portGroupProvider;
+    }
+
+    public void unsetPortGroupProvider(PortGroupProvider portGroupProvider) {
+        this.portGroupProvider = null;
+    }
+
+    public void setPortGroupProvider(PortGroupProvider portGroupProvider) {
+        this.portGroupProvider = portGroupProvider;
+        portGroupProvider.registerPortGroupChange(this);
+        for (PortGroupConfig config : portGroupConfigs.values()) {
+            portGroupProvider.createPortGroupConfig(config);
+        }
+    }
+
+    public void setHostFinder(IfIptoHost hostFinder) {
+        this.hostFinder = hostFinder;
+    }
+
+    public void unsetHostFinder(IfIptoHost hostFinder) {
+        if (this.hostFinder == hostFinder) {
+            this.hostFinder = null;
+        }
+    }
+
+    public void setFrmAware(IForwardingRulesManagerAware obj) {
+        this.frmAware.add(obj);
+    }
+
+    public void unsetFrmAware(IForwardingRulesManagerAware obj) {
+        this.frmAware.remove(obj);
+    }
+
+    void setClusterContainerService(IClusterContainerServices s) {
+        log.debug("Cluster Service set");
+        this.clusterContainerService = s;
+    }
+
+    void unsetClusterContainerService(IClusterContainerServices s) {
+        if (this.clusterContainerService == s) {
+            log.debug("Cluster Service removed!");
+            this.clusterContainerService = null;
+        }
+    }
+
+    private String getContainerName() {
+        if (container == null) {
+            return GlobalConstants.DEFAULT.toString();
+        }
+        return container.getName();
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        log.info("Init");
+        frmAware = Collections
+                .synchronizedSet(new HashSet<IForwardingRulesManagerAware>());
+        frmFileName = GlobalConstants.STARTUPHOME.toString() + "frm_staticflows_"
+                + this.getContainerName() + ".conf";
+        portGroupFileName = GlobalConstants.STARTUPHOME.toString() + "portgroup_"
+                + this.getContainerName() + ".conf";
+
+        inContainerMode = false;
+
+        if (portGroupProvider != null) {
+            portGroupProvider.registerPortGroupChange(this);
+        }
+
+        nonClusterObjectCreate();
+
+        cacheStartup();
+
+        registerWithOSGIConsole();
+
+        /*
+         * If we are not the first cluster node to come up, do not initialize
+         * the static flow entries ordinal
+         */
+        if (staticFlowsOrdinal.size() == 0) {
+            staticFlowsOrdinal.put(0, Integer.valueOf(0));
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when at least one dependency
+     * become unsatisfied or when the component is shutting down because for
+     * example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        log.info("Destroy");
+        destroyCaches();
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called and after
+     * the services provided by the class are registered in the service registry
+     *
+     */
+    void start() {
+        log.info("Start");
+        /*
+         * Read startup and build database if we have not already gotten the
+         * configurations synced from another node
+         */
+        if (staticFlows.isEmpty()) {
+            loadFlowConfiguration();
+        }
+    }
+
+    /**
+     * Function called by the dependency manager before the services exported by
+     * the component are unregistered, this will be followed by a "destroy ()"
+     * calls
+     *
+     */
+    void stop() {
+        log.info("Stop");
+    }
+
+    public void setFlowProgrammerService(IFlowProgrammerService service) {
+        this.programmer = service;
+    }
+
+    public void unsetFlowProgrammerService(IFlowProgrammerService service) {
+        if (this.programmer == service) {
+            this.programmer = null;
+        }
+    }
+
+    public void setSwitchManager(ISwitchManager switchManager) {
+        this.switchManager = switchManager;
+    }
+
+    public void unsetSwitchManager(ISwitchManager switchManager) {
+        if (this.switchManager == switchManager) {
+            this.switchManager = null;
+        }
+    }
+
+    @Override
+    public void tagUpdated(String containerName, Node n, short oldTag,
+            short newTag, UpdateType t) {
+
+    }
+
+    @Override
+    public void containerFlowUpdated(String containerName,
+            ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) {
+        /*
+         * Whether it is an addition or removal, we have to recompute the
+         * merged flows entries taking into account all the current container flows
+         * because flow merging is not an injective function
+         */
+        updateFlowsContainerFlow();
+    }
+
+    @Override
+    public void nodeConnectorUpdated(String containerName, NodeConnector p,
+            UpdateType t) {
+        // No action
+    }
+
+    @Override
+    public void containerModeUpdated(UpdateType update) {
+        switch (update) {
+        case ADDED:
+            this.inContainerMode = true;
+            this.uninstallAllFlowEntries();
+            break;
+        case REMOVED:
+            this.inContainerMode = false;
+            this.reinstallAllFlowEntries();
+            break;
+        default:
+        }
+
+        // Update our configuration DB
+        updateStaticFlowConfigsOnContainerModeChange(update);
+    }
+
+    /*
+     * OSGI COMMANDS
+     */
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---FRM Matrix Application---\n");
+        help.append("\t printMatrixData        - Prints the Matrix Configs\n");
+        help.append("\t addMatrixConfig <name> <regex>\n");
+        help.append("\t delMatrixConfig <name>\n");
+        help.append("\t useMatrixConfig <name>\n");
+        return help.toString();
+    }
+
+    public void _printMatrixData(CommandInterpreter ci) {
+        ci.println("Configs : ");
+        ci.println("---------");
+        ci.println(portGroupConfigs);
+
+        ci.println("Data : ");
+        ci.println("------");
+        ci.println(portGroupData);
+    }
+
+    public void _addMatrixConfig(CommandInterpreter ci) {
+        String name = ci.nextArgument();
+        String regex = ci.nextArgument();
+        addPortGroupConfig(name, regex, false);
+    }
+
+    public void _delMatrixConfig(CommandInterpreter ci) {
+        String name = ci.nextArgument();
+        delPortGroupConfig(name);
+    }
+
+    public void _useMatrixConfig(CommandInterpreter ci) {
+        String name = ci.nextArgument();
+        usePortGroupConfig(name);
+    }
+
+    public void _arpPunt(CommandInterpreter ci) {
+        String switchId = ci.nextArgument();
+        long swid = HexEncode.stringToLong(switchId);
+        Node node = NodeCreator.createOFNode(swid);
+        installImplicitARPReplyPunt(node);
+    }
+
+    public void _frmaddflow(CommandInterpreter ci) throws UnknownHostException {
+        Node node = null;
+        String nodeId = ci.nextArgument();
+        if (nodeId == null) {
+            ci.print("Node id not specified");
+            return;
+        }
+        try {
+            node = NodeCreator.createOFNode(Long.valueOf(nodeId));
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+        }
+        ci.println(this.programmer.addFlow(node, getSampleFlow(node)));
+    }
+
+    public void _frmremoveflow(CommandInterpreter ci)
+            throws UnknownHostException {
+        Node node = null;
+        String nodeId = ci.nextArgument();
+        if (nodeId == null) {
+            ci.print("Node id not specified");
+            return;
+        }
+        try {
+            node = NodeCreator.createOFNode(Long.valueOf(nodeId));
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+        }
+        ci.println(this.programmer.removeFlow(node, getSampleFlow(node)));
+    }
+
+    private Flow getSampleFlow(Node node) throws UnknownHostException {
+        NodeConnector port = NodeConnectorCreator.createOFNodeConnector(
+                (short) 24, node);
+        NodeConnector oport = NodeConnectorCreator.createOFNodeConnector(
+                (short) 30, node);
+        byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
+                (byte) 0x9a, (byte) 0xbc };
+        byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d,
+                (byte) 0x5e, (byte) 0x6f };
+        InetAddress srcIP = InetAddress.getByName("172.28.30.50");
+        InetAddress dstIP = InetAddress.getByName("171.71.9.52");
+        InetAddress ipMask = InetAddress.getByName("255.255.255.0");
+        InetAddress ipMask2 = InetAddress.getByName("255.0.0.0");
+        short ethertype = EtherTypes.IPv4.shortValue();
+        short vlan = (short) 27;
+        byte vlanPr = 3;
+        Byte tos = 4;
+        byte proto = IPProtocols.TCP.byteValue();
+        short src = (short) 55000;
+        short dst = 80;
+
+        /*
+         * Create a SAL Flow aFlow
+         */
+        Match match = new Match();
+        match.setField(MatchType.IN_PORT, port);
+        match.setField(MatchType.DL_SRC, srcMac);
+        match.setField(MatchType.DL_DST, dstMac);
+        match.setField(MatchType.DL_TYPE, ethertype);
+        match.setField(MatchType.DL_VLAN, vlan);
+        match.setField(MatchType.DL_VLAN_PR, vlanPr);
+        match.setField(MatchType.NW_SRC, srcIP, ipMask);
+        match.setField(MatchType.NW_DST, dstIP, ipMask2);
+        match.setField(MatchType.NW_TOS, tos);
+        match.setField(MatchType.NW_PROTO, proto);
+        match.setField(MatchType.TP_SRC, src);
+        match.setField(MatchType.TP_DST, dst);
+
+        List<Action> actions = new ArrayList<Action>();
+        actions.add(new Output(oport));
+        actions.add(new PopVlan());
+        actions.add(new Flood());
+        actions.add(new Controller());
+        return new Flow(match, actions);
+    }
+
+    @Override
+    public Status saveConfiguration() {
+        return  saveConfig();
+    }
+
+    public void _frmNodeFlows(CommandInterpreter ci) {
+        boolean verbose = false;
+        String verboseCheck = ci.nextArgument();
+        if (verboseCheck != null) {
+            verbose = verboseCheck.equals("true");
+        }
+
+        // Dump per node database
+        for (Entry<Node, Set<FlowEntryInstall>> entry : this.nodeFlows
+                .entrySet()) {
+            Node node = entry.getKey();
+            for (FlowEntryInstall flow : entry.getValue()) {
+                if (!verbose) {
+                    ci.println(node + " " + flow.getFlowName());
+                } else {
+                    ci.println(node + " " + flow.toString());
+                }
+            }
+        }
+    }
+
+    public void _frmGroupFlows(CommandInterpreter ci) {
+        boolean verbose = false;
+        String verboseCheck = ci.nextArgument();
+        if (verboseCheck != null) {
+            verbose = verboseCheck.equals("true");
+        }
+
+        // Dump per node database
+        for (Entry<String, Set<FlowEntryInstall>> entry : this.groupFlows
+                .entrySet()) {
+            String group = entry.getKey();
+            ci.println("Group " + group + ":");
+            for (FlowEntryInstall flow : entry.getValue()) {
+                if (!verbose) {
+                    ci.println(flow.getNode() + " " + flow.getFlowName());
+                } else {
+                    ci.println(flow.getNode() + " " + flow.toString());
+                }
+            }
+        }
+    }
+
+}
diff --git a/opendaylight/forwardingrulesmanager/src/test/java/org/opendaylight/controller/forwardingrulesmanager/frmTest.java b/opendaylight/forwardingrulesmanager/src/test/java/org/opendaylight/controller/forwardingrulesmanager/frmTest.java
new file mode 100644 (file)
index 0000000..dd2fd22
--- /dev/null
@@ -0,0 +1,714 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwardingrulesmanager;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.ActionType;
+import org.opendaylight.controller.sal.action.Controller;
+import org.opendaylight.controller.sal.action.Flood;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.PopVlan;
+import org.opendaylight.controller.sal.action.SetDlDst;
+import org.opendaylight.controller.sal.action.SetNwDst;
+import org.opendaylight.controller.sal.action.SetVlanId;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.IPProtocols;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+public class frmTest {
+
+       @Test
+       public void testFlowEntryInstall() throws UnknownHostException{
+                Node node = NodeCreator.createOFNode(1L);
+            FlowEntry pol = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                       node);
+            FlowEntry pol2 = new FlowEntry("polTest2", null, getSampleFlowV6(node),
+                       node);
+            FlowEntryInstall fei = new FlowEntryInstall(pol, null);
+            FlowEntryInstall fei2 = new FlowEntryInstall(pol, null);
+            FlowEntryInstall fei3 = new FlowEntryInstall(pol2, null);
+            Assert.assertTrue(fei.getOriginal().equals(pol));
+            Assert.assertTrue(fei.getInstall().equals(pol));
+            Assert.assertTrue(fei.getFlowName().equals(pol.getFlowName()));
+            Assert.assertTrue(fei.getGroupName().equals(pol.getGroupName()));
+            Assert.assertTrue(fei.getNode().equals(pol.getNode()));
+            Assert.assertFalse(fei.isDeletePending());
+            fei.toBeDeleted();
+            Assert.assertTrue(fei.isDeletePending());
+            Assert.assertNull(fei.getContainerFlow());
+            Assert.assertTrue(fei.equalsByNodeAndName(pol.getNode(), pol.getFlowName()));
+            
+            Assert.assertTrue(fei.equals(fei2));
+            fei2.getOriginal().setFlowName("polTest2");
+            Assert.assertFalse(fei.equals(null));
+            Assert.assertFalse(fei.equals(fei3));
+         
+       }
+    @Test
+    public void testFlowEntryCreation() throws UnknownHostException {
+        Node node = NodeCreator.createOFNode(1L);
+        FlowEntry pol = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                node);
+        Assert.assertTrue(pol.getFlow().equals(getSampleFlowV6(node)));
+    }
+
+    @Test
+    public void testFlowEntrySetGet() throws UnknownHostException {
+        Node node = NodeCreator.createOFNode(1L);
+        Node node2 = NodeCreator.createOFNode(2L);
+        FlowEntry pol = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                node);
+        pol.setGroupName("polTest2");
+        pol.setFlowName("flowName");
+        Assert.assertTrue(pol.getFlowName().equals("flowName"));
+        Assert.assertTrue(pol.getGroupName().equals("polTest2"));
+        pol.setNode(node2);
+        Assert.assertTrue(pol.getNode().equals(node2));
+        Assert.assertTrue(pol.equalsByNodeAndName(node2, "flowName"));
+    }
+
+    @Test
+    public void testFlowEntryEquality() throws UnknownHostException {
+        Node node = NodeCreator.createOFNode(1L);
+        Node node2 = NodeCreator.createOFNode(1L);
+        FlowEntry pol = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                node);
+        FlowEntry pol2 = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                node2);
+        Assert.assertTrue(pol.equals(pol2));
+    }
+
+
+    @Test
+    public void testFlowEntryCloning() throws UnknownHostException {
+        Node node = NodeCreator.createOFNode(1L);
+        FlowEntry pol = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                node);
+        FlowEntry pol2 = pol.clone();
+        Assert.assertTrue(pol.equals(pol2));
+    }
+
+    @Test
+    public void testFlowEntrySet() throws UnknownHostException {
+        Set<FlowEntry> set = new HashSet<FlowEntry>();
+
+        Node node1 = NodeCreator.createOFNode(1L);
+        Node node2 = NodeCreator.createOFNode(2L);
+        Node node3 = NodeCreator.createOFNode(3L);
+
+        Match match = new Match();
+        match.setField(MatchType.NW_SRC, InetAddress.getAllByName("1.1.1.1"));
+        match.setField(MatchType.NW_DST, InetAddress.getAllByName("2.2.2.2"));
+        match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
+
+        List<Action> actionList = new ArrayList<Action>();
+        //actionList.add(new Drop());
+
+        Flow flow = new Flow(match, actionList);
+        FlowEntry pol1 = new FlowEntry("m1", "same", flow, node1);
+        FlowEntry pol2 = new FlowEntry("m2", "same", flow, node2);
+        FlowEntry pol3 = new FlowEntry("m3", "same", flow, node3);
+
+        set.add(pol1);
+        set.add(pol2);
+        set.add(pol3);
+
+        Assert.assertTrue(set.contains(pol1));
+        Assert.assertTrue(set.contains(pol2));
+        Assert.assertTrue(set.contains(pol3));
+
+        Assert.assertTrue(set.contains(pol1.clone()));
+        Assert.assertTrue(set.contains(pol2.clone()));
+        Assert.assertTrue(set.contains(pol3.clone()));
+
+    }
+
+    @Test
+    public void testInternalFlow() {
+        FlowConfig flowConfig = new FlowConfig();
+        Assert.assertFalse(flowConfig.isInternalFlow());
+        flowConfig.setName("**Internal");
+        Assert.assertTrue(flowConfig.isInternalFlow());
+        flowConfig.setName("External");
+        Assert.assertFalse(flowConfig.isInternalFlow());
+    }
+
+    @Test
+    public void testFlowConfigCreateSet() throws UnknownHostException {
+        FlowConfig frmC = new FlowConfig();
+        FlowConfig frmC3 = new FlowConfig();
+        Node node = NodeCreator.createOFNode(1L);
+        FlowEntry entry = new FlowEntry("polTest", null, getSampleFlowV6(node),
+                node);
+
+        //testing equal function
+        Assert.assertFalse(frmC.equals(null));
+        Assert.assertTrue(frmC.equals(frmC));
+        Assert.assertTrue(frmC.equals(frmC3));
+        Assert.assertFalse(frmC.equals(entry));
+        FlowConfig flowC = createSampleFlowConfig();
+        Assert.assertFalse(frmC.equals(flowC));
+        //testing installInHW
+        Assert.assertTrue(frmC.installInHw());
+        frmC.setInstallInHw(false);
+        Assert.assertFalse(frmC.installInHw());
+        frmC.setInstallInHw(true);
+        Assert.assertTrue(frmC.installInHw());
+
+        //testing general set and get methods
+        ArrayList<String> actions = createSampleActionList();
+        frmC.setActions(actions);
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setActions(actions);
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setCookie("0");
+        Assert.assertTrue(frmC.getCookie().equals("0"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setCookie("0");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setDstMac("00:A0:C9:22:AB:11");
+        Assert.assertTrue(frmC.getDstMac().equals("00:A0:C9:22:AB:11"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setDstMac("00:A0:C9:22:AB:11");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setSrcMac("00:A0:C9:14:C8:29");
+        Assert.assertTrue(frmC.getSrcMac().equals("00:A0:C9:14:C8:29"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setSrcMac("00:A0:C9:14:C8:29");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setDynamic(true);
+        Assert.assertTrue(frmC.isDynamic());
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setDynamic(true);
+        flowC.setDynamic(true);
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setEtherType("0x0800");
+        Assert.assertTrue(frmC.getEtherType().equals("0x0800"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setEtherType("0x0800");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setIngressPort("60");
+        Assert.assertTrue(frmC.getIngressPort().equals("60"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setIngressPort("60");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setName("Config1");
+        Assert.assertTrue(frmC.getName().equals("Config1"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setName("Config1");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setDstIp("2.2.2.2");
+        Assert.assertTrue(frmC.getDstIp().equals("2.2.2.2"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setDstIp("2.2.2.2");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setSrcIp("1.2.3.4");
+        Assert.assertTrue(frmC.getSrcIp().equals("1.2.3.4"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setSrcIp("1.2.3.4");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        Assert.assertFalse(frmC.isPortGroupEnabled());
+        frmC.setPortGroup("2");
+        Assert.assertTrue(frmC.isPortGroupEnabled());
+        Assert.assertTrue(frmC.getPortGroup().equals("2"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setPortGroup("2");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setPriority("100");
+        Assert.assertTrue(frmC.getPriority().equals("100"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setPriority("100");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setProtocol(IPProtocols.TCP.toString());
+        Assert.assertTrue(frmC.getProtocol().equals(
+                              IPProtocols.TCP.toString()));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setProtocol(IPProtocols.TCP.toString());
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                     "1"));
+        Assert.assertTrue(frmC.getNode()
+                          .equals(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                                  "1")));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                      "1"));
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setTosBits("0");
+        Assert.assertTrue(frmC.getTosBits().equals("0"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setTosBits("0");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setDstPort("100");
+        Assert.assertTrue(frmC.getDstPort().equals("100"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setDstPort("100");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setSrcPort("8080");
+        Assert.assertTrue(frmC.getSrcPort().equals("8080"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setSrcPort("8080");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setVlanId("100");
+        Assert.assertTrue(frmC.getVlanId().equals("100"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setVlanId("100");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setVlanPriority("0");
+        Assert.assertTrue(frmC.getVlanPriority().equals("0"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setVlanPriority("0");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setIdleTimeout("300");
+        Assert.assertTrue(frmC.getIdleTimeout().equals("300"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setIdleTimeout("300");
+
+        Assert.assertFalse(frmC.equals(flowC));
+        frmC.setHardTimeout("1000");
+        Assert.assertTrue(frmC.getHardTimeout().equals("1000"));
+        Assert.assertFalse(frmC.equals(frmC3));
+        frmC3.setHardTimeout("1000");
+
+        //     Assert.assertFalse(frmC.equals(flowC));
+        Assert.assertTrue(actions.equals(frmC.getActions()));
+
+        FlowConfig frmC2 = new FlowConfig(frmC);
+
+        Assert.assertFalse(frmC2.equals(frmC));
+        frmC2.setDynamic(false);
+        Assert.assertFalse(frmC2.equals(frmC));
+        frmC2.setDynamic(true);
+        Assert.assertTrue(frmC2.equals(frmC));
+        //Assert.assertFalse(frmC2.equals(frmC3));
+        flowC.setDynamic(true);
+        Assert.assertTrue(flowC.equals(frmC));
+        Assert.assertTrue(flowC.isStatusSuccessful());
+        flowC.setStatus("Invalid");
+        Assert.assertFalse(flowC.isStatusSuccessful());
+
+        flowC.getActions().add(ActionType.DROP.toString());
+        Assert.assertFalse(flowC.equals(frmC));
+        Assert.assertFalse(flowC.isIPv6());
+        flowC.setDstIp("2001:420:281:1004:407a:57f4:4d15:c355");
+        Assert.assertTrue(flowC.isIPv6());
+        flowC.setSrcIp("2001:420:281:1004:407a:57f4:4d15:c355");
+        Assert.assertTrue(flowC.isIPv6());
+
+        Long id = (Long) flowC.getNode().getID();
+        Assert.assertTrue(id.toString().equals("1"));
+
+    }
+    
+    @Test
+    public void testFlowConfigNextHopValidity() throws UnknownHostException{
+       FlowConfig fc = new FlowConfig();
+       Assert.assertFalse(fc.isOutputNextHopValid(null));
+       Assert.assertFalse(fc.isOutputNextHopValid("abc"));
+       Assert.assertFalse(fc.isOutputNextHopValid("1.1.1"));
+       Assert.assertFalse(fc.isOutputNextHopValid("1.1.1.1/49"));
+       
+       Assert.assertTrue(fc.isOutputNextHopValid("1.1.1.1"));
+       Assert.assertTrue(fc.isOutputNextHopValid("1.1.1.1/32"));
+       Assert.assertTrue(fc.isOutputNextHopValid("2001:420:281:1004:407a:57f4:4d15:c355"));
+       
+    }
+    
+    @Test
+    public void testFlowConfigEqualities() throws UnknownHostException{
+       FlowConfig fc = new FlowConfig();
+       FlowConfig fc2 = new FlowConfig();
+       fc.setName("flow1");
+       fc.setNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                   "1"));
+       Assert.assertFalse(fc.onNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                                     "0")));
+       Assert.assertTrue(fc.onNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                                    "1")));
+       
+       Assert.assertTrue(fc.isByNameAndNodeIdEqual(
+                              "flow1",
+                              Node.fromString(Node.NodeIDType.OPENFLOW, "1")));
+       Assert.assertFalse(fc.isByNameAndNodeIdEqual(
+                               "flow1",
+                               Node.fromString(Node.NodeIDType.OPENFLOW, "0")));
+       Assert.assertFalse(fc.isByNameAndNodeIdEqual(
+                               "flow2",
+                               Node.fromString(Node.NodeIDType.OPENFLOW, "1")));
+       
+       Assert.assertFalse(fc.isByNameAndNodeIdEqual(fc2));
+       fc2.setName("flow1");
+       Assert.assertFalse(fc.isByNameAndNodeIdEqual(fc2));
+       fc2.setNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                     "0"));
+       Assert.assertFalse(fc.isByNameAndNodeIdEqual(fc2));
+       fc2.setNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                    "1"));
+       Assert.assertTrue(fc.isByNameAndNodeIdEqual(fc2));
+    }
+    
+    @Test
+    public void testStatusToggle() throws UnknownHostException{
+       FlowConfig fc = new FlowConfig();
+       fc.toggleStatus();
+       Assert.assertTrue(fc.installInHw());
+       fc.toggleStatus();
+       Assert.assertFalse(fc.installInHw());
+       fc.toggleStatus();
+       Assert.assertTrue(fc.installInHw());
+       
+    }
+    @Test
+    public void testGetFlowEntry() throws UnknownHostException {
+        FlowConfig fc2 = createSampleFlowConfig();
+        FlowEntry fe = fc2.getFlowEntry();
+        Assert.assertNotNull(fe);
+    }
+
+    @Test
+    public void testGetFlow() throws UnknownHostException {
+        FlowConfig fc = new FlowConfig();
+        fc.setActions(createSampleActionList());
+        Flow flow = fc.getFlow();
+        Assert.assertNotNull(flow);
+    }
+
+    @Test
+    public void testL2AddressValid() {
+        FlowConfig fc = new FlowConfig();
+        Assert.assertFalse(fc.isL2AddressValid(null));
+        Assert.assertFalse(fc.isL2AddressValid("11"));
+        Assert.assertFalse(fc.isL2AddressValid("00:A0:C9:14:C8:"));
+        Assert.assertFalse(fc.isL2AddressValid("000:A01:C9:14:C8:211"));
+
+        Assert.assertTrue(fc.isL2AddressValid("00:A0:C9:14:C8:29"));
+    }
+
+    @Test
+    public void testValid() throws UnknownHostException {
+        StringBuffer sb = new StringBuffer();
+        sb.setLength(0);
+        FlowConfig fc2 = createSampleFlowConfig();
+        Assert.assertTrue(fc2.isValid(null, sb));
+
+        FlowConfig fc = new FlowConfig();
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Name is null"));
+
+        fc.setName("Config");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Node is null"));
+
+        fc.setNode(Node.fromString(Node.NodeIDType.OPENFLOW,
+                                   "1"));
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setPriority("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains(
+                "is not in the range 0 - 65535"));
+        sb.setLength(0);
+
+        fc.setPriority("100000");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains(
+                "is not in the range 0 - 65535"));
+        sb.setLength(0);
+        fc.setPriority("2000");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setCookie("100");
+        fc.setIngressPort("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert
+                .assertTrue(sb.toString().contains(
+                        "is not valid for the Switch"));
+        fc.setIngressPort("100");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setVlanId(("-1"));
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString()
+                .contains("is not in the range 0 - 4095"));
+        sb.setLength(0);
+        fc.setVlanId("5000");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString()
+                .contains("is not in the range 0 - 4095"));
+        fc.setVlanId("100");
+        Assert.assertTrue(fc.isValid(null, sb));
+        fc.setVlanPriority("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("is not in the range 0 - 7"));
+        sb.setLength(0);
+        fc.setVlanPriority("9");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("is not in the range 0 - 7"));
+        fc.setVlanPriority("5");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setEtherType("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Ethernet type"));
+        sb.setLength(0);
+        fc.setEtherType("0xfffff");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Ethernet type"));
+        fc.setEtherType("0x800");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setTosBits("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("IP ToS bits"));
+        fc.setTosBits("65");
+        sb.setLength(0);
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("IP ToS bits"));
+        fc.setTosBits("60");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setSrcPort("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Transport source port"));
+        sb.setLength(0);
+        fc.setSrcPort("0xfffff");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Transport source port"));
+        fc.setSrcPort("0x00ff");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setDstPort("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Transport destination port"));
+        sb.setLength(0);
+        fc.setDstPort("0xfffff");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Transport destination port"));
+        fc.setDstPort("0x00ff");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setSrcMac("abc");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Ethernet source address"));
+        sb.setLength(0);
+        fc.setSrcMac("00:A0:C9:14:C8:29");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setDstMac("abc");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString()
+                .contains("Ethernet destination address"));
+        fc.setDstMac("00:A0:C9:22:AB:11");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setSrcIp("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("IP source address"));
+        fc.setSrcIp("2001:420:281:1004:407a:57f4:4d15:c355");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains(
+                "Type mismatch between Ethernet & Src IP"));
+
+        fc.setEtherType("0x86dd");
+        Assert.assertTrue(fc.isValid(null, sb));
+        sb.setLength(0);
+
+        fc.setSrcIp("1.1.1.1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains(
+                "Type mismatch between Ethernet & Src IP"));
+        fc.setEtherType("0x800");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setDstIp("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("IP destination address"));
+        fc.setDstIp("2001:420:281:1004:407a:57f4:4d15:c355");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains(
+                "Type mismatch between Ethernet & Dst IP"));
+
+        fc.setEtherType("0x86dd");
+        fc.setSrcIp("2001:420:281:1004:407a:57f4:4d15:c355");
+        Assert.assertTrue(fc.isValid(null, sb));
+        sb.setLength(0);
+
+        fc.setDstIp("2.2.2.2");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains(
+                "Type mismatch between Ethernet & Dst IP"));
+        fc.setEtherType("0x800");
+        fc.setSrcIp("1.1.1.1");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setEtherType(null);
+        fc.setSrcIp("2001:420:281:1004:407a:57f4:4d15:c355");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("IP Src Dest Type mismatch"));
+        fc.setSrcIp("1.1.1.1");
+        fc.setIdleTimeout("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Idle Timeout value"));
+        sb.setLength(0);
+        fc.setIdleTimeout("0xfffff");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Idle Timeout value"));
+        fc.setIdleTimeout("10");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+        fc.setHardTimeout("-1");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Hard Timeout value"));
+        fc.setHardTimeout("0xfffff");
+        Assert.assertFalse(fc.isValid(null, sb));
+        Assert.assertTrue(sb.toString().contains("Hard Timeout value"));
+        fc.setHardTimeout("10");
+        Assert.assertTrue(fc.isValid(null, sb));
+
+    }
+
+    private FlowConfig createSampleFlowConfig() throws UnknownHostException {
+        ArrayList<String> actions;
+        actions = createSampleActionList();
+        //actions.add(ActionType.CONTROLLER.toString());
+        FlowConfig flowConfig =
+            new FlowConfig("true", "Config1", 
+                           Node.fromString(Node.NodeIDType.OPENFLOW,
+                                           "1"), "100", "0", "60", "2", "100",
+                           "0", "0x0800", "00:A0:C9:14:C8:29",
+                           "00:A0:C9:22:AB:11", IPProtocols.TCP.toString(), "0",
+                           "1.2.3.4", "2.2.2.2", "8080", "100", "300", "1000",
+                           actions);
+        return flowConfig;
+
+    }
+
+    private ArrayList<String> createSampleActionList() {
+        ArrayList<String> actions = new ArrayList<String>();
+        actions.add(ActionType.DROP.toString());
+        actions.add(ActionType.LOOPBACK.toString());
+        actions.add(ActionType.FLOOD.toString());
+        actions.add(ActionType.SW_PATH.toString());
+        actions.add(ActionType.HW_PATH.toString());
+        actions.add(ActionType.SET_VLAN_PCP.toString()+"=1");
+        actions.add(ActionType.SET_VLAN_ID.toString()+"=1");
+        actions.add(ActionType.POP_VLAN.toString());
+        actions.add(ActionType.SET_DL_SRC.toString()+"=00:A0:C1:AB:22:11");
+        actions.add(ActionType.SET_DL_DST.toString()+"=00:B1:C1:00:AA:BB");
+        actions.add(ActionType.SET_NW_SRC.toString()+"=1.1.1.1");
+        actions.add(ActionType.SET_NW_DST.toString()+"=2.2.2.2");
+        actions.add(ActionType.CONTROLLER.toString());
+        actions.add(ActionType.SET_NW_TOS.toString()+"1");
+        actions.add(ActionType.SET_TP_SRC.toString()+"60");
+        actions.add(ActionType.SET_TP_DST.toString()+"8080");
+        actions.add(ActionType.SET_NEXT_HOP.toString()+"=1.1.1.1");
+        
+        return actions;
+    }
+
+    private Flow getSampleFlowV6(Node node) throws UnknownHostException {
+        NodeConnector port = NodeConnectorCreator.createOFNodeConnector(
+                (short) 24, node);
+        NodeConnector oport = NodeConnectorCreator.createOFNodeConnector(
+                (short) 30, node);
+        byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
+                (byte) 0x9a, (byte) 0xbc };
+        byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d,
+                (byte) 0x5e, (byte) 0x6f };
+        byte newMac[] = { (byte) 0x11, (byte) 0xaa, (byte) 0xbb, (byte) 0x34,
+                (byte) 0x9a, (byte) 0xee };
+        InetAddress srcIP = InetAddress
+                .getByName("2001:420:281:1004:407a:57f4:4d15:c355");
+        InetAddress dstIP = InetAddress
+                .getByName("2001:420:281:1004:e123:e688:d655:a1b0");
+        InetAddress ipMask = InetAddress
+                .getByName("ffff:ffff:ffff:ffff:0:0:0:0");
+        InetAddress ipMask2 = InetAddress
+                .getByName("ffff:ffff:ffff:ffff:ffff:ffff:ffff:0");
+        InetAddress newIP = InetAddress.getByName("2056:650::a1b0");
+        short ethertype = EtherTypes.IPv6.shortValue();
+        short vlan = (short) 27;
+        byte vlanPr = (byte) 3;
+        Byte tos = 4;
+        byte proto = IPProtocols.UDP.byteValue();
+        short src = (short) 5500;
+        short dst = 80;
+
+        /*
+         * Create a SAL Flow aFlow
+         */
+        Match match = new Match();
+        match.setField(MatchType.IN_PORT, port);
+        match.setField(MatchType.DL_SRC, srcMac);
+        match.setField(MatchType.DL_DST, dstMac);
+        match.setField(MatchType.DL_TYPE, ethertype);
+        match.setField(MatchType.DL_VLAN, vlan);
+        match.setField(MatchType.DL_VLAN_PR, vlanPr);
+        match.setField(MatchType.NW_SRC, srcIP, ipMask);
+        match.setField(MatchType.NW_DST, dstIP, ipMask2);
+        match.setField(MatchType.NW_TOS, tos);
+        match.setField(MatchType.NW_PROTO, proto);
+        match.setField(MatchType.TP_SRC, src);
+        match.setField(MatchType.TP_DST, dst);
+
+        List<Action> actions = new ArrayList<Action>();
+        actions.add(new Controller());
+        actions.add(new SetVlanId(5));
+        actions.add(new SetDlDst(newMac));
+        actions.add(new SetNwDst(newIP));
+        actions.add(new Output(oport));
+        actions.add(new PopVlan());
+        actions.add(new Flood());
+
+        actions.add(new Controller());
+
+        Flow flow = new Flow(match, actions);
+        flow.setPriority((short) 300);
+        flow.setHardTimeout((short) 240);
+
+        return flow;
+    }
+}
diff --git a/opendaylight/hosttracker/pom.xml b/opendaylight/hosttracker/pom.xml
new file mode 100644 (file)
index 0000000..b7628dd
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+       xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../commons/opendaylight</relativePath>
+       </parent>
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>hosttracker</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Export-Package>
+                                                       org.opendaylight.controller.hosttracker,
+                                                       org.opendaylight.controller.hosttracker.hostAware
+                                               </Export-Package>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.hosttracker,
+                                                       org.opendaylight.controller.hosttracker.hostAware,
+                                                       org.opendaylight.controller.topologymanager,
+                                                       org.opendaylight.controller.sal.packet.address,
+                                                       org.opendaylight.controller.switchmanager,
+                                                       org.opendaylight.controller.clustering.services,
+                                                       javax.xml.bind.annotation,
+                                                       javax.xml.bind,
+                                                       org.apache.felix.dm,
+                                                       org.apache.commons.lang3.builder,
+                                                       org.osgi.service.component,
+                                                       org.slf4j,
+                                                       org.eclipse.osgi.framework.console,
+                                                       org.osgi.framework
+                                               </Import-Package>
+                                               <Bundle-Activator>
+                                                       org.opendaylight.controller.hosttracker.internal.Activator
+                                               </Bundle-Activator>
+                                               <Service-Component>
+                                               </Service-Component>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>topologymanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>switchmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>clustering.services</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/HostTracker.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/HostTracker.java
new file mode 100644 (file)
index 0000000..dbcd0ee
--- /dev/null
@@ -0,0 +1,1360 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+import org.opendaylight.controller.sal.core.Tier;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.packet.address.DataLinkAddress;
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
+import org.opendaylight.controller.switchmanager.Subnet;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @file   HostTracker.java
+ * This class tracks the location of IP Hosts as to which Switch, Port, VLAN, they are 
+ * connected to, as well as their MAC address. This is done dynamically as well as statically.
+ * The dynamic mechanism consists of listening to ARP messages as well sending ARP requests.
+ * Static mechanism consists of Northbound APIs to add or remove the hosts from the local
+ * database. ARP aging is also implemented to age out dynamically learned hosts. Interface
+ * methods are provided for other applications to
+ *  1. Query the local database for a single host
+ *  2. Get a list of all hosts
+ *  3. Get notification if a host is learned/added or removed the database
+ */
+
+public class HostTracker implements IfIptoHost, IfHostListener,
+        ISwitchManagerAware, IInventoryListener, ITopologyManagerAware {
+    private static final Logger logger = LoggerFactory
+            .getLogger(HostTracker.class);
+    private IHostFinder hostFinder;
+    private ConcurrentMap<InetAddress, HostNodeConnector> hostsDB;
+    /* Following is a list of hosts which have been requested by NB APIs to be added,
+     * but either the switch or the port is not sup, so they will be added here until
+     * both come up
+     */
+    private ConcurrentMap<NodeConnector, HostNodeConnector> inactiveStaticHosts;
+    private Set<IfNewHostNotify> newHostNotify = Collections
+            .synchronizedSet(new HashSet<IfNewHostNotify>());
+
+    private ITopologyManager topologyManager;
+    private IClusterContainerServices clusterContainerService = null;
+    private ISwitchManager switchManager = null;
+    private Timer timer;
+    private Timer arp_refresh_timer;
+    private String containerName = null;
+
+    private static class ARPPending {
+        protected InetAddress hostIP;
+        protected short sent_count;
+        protected HostTrackerCallable hostTrackerCallable;
+
+        public InetAddress getHostIP() {
+            return hostIP;
+        }
+
+        public short getSent_count() {
+            return sent_count;
+        }
+
+        public HostTrackerCallable getHostTrackerCallable() {
+            return hostTrackerCallable;
+        }
+
+        public void setHostIP(InetAddress networkAddr) {
+            this.hostIP = networkAddr;
+        }
+
+        public void setSent_count(short count) {
+            this.sent_count = count;
+        }
+
+        public void setHostTrackerCallable(HostTrackerCallable callable) {
+            hostTrackerCallable = callable;
+        }
+    }
+
+    //This list contains the hosts for which ARP requests are being sent periodically
+    private List<ARPPending> ARPPendingList = new ArrayList<HostTracker.ARPPending>();
+    /*
+     * This list below contains the hosts which were initially in ARPPendingList above,
+     * but ARP response didn't come from there hosts after multiple attempts over 8
+     * seconds. The assumption is that the response didn't come back due to one of the
+     * following possibilities:
+     *   1. The L3 interface wasn't created for this host in the controller. This would
+     *      cause arphandler not to know where to send the ARP
+     *   2. The host facing port is down
+     *   3. The IP host doesn't exist or is not responding to ARP requests
+     *
+     * Conditions 1 and 2 above can be recovered if ARP is sent when the relevant L3
+     * interface is added or the port facing host comes up. Whenever L3 interface is
+     * added or host facing port comes up, ARP will be sent to hosts in this list.
+     *
+     * We can't recover from condition 3 above
+     */
+    private ArrayList<ARPPending> failedARPReqList = new ArrayList<HostTracker.ARPPending>();
+
+    public HostTracker() {
+    }
+
+    private void startUp() {
+        allocateCache();
+        retrieveCache();
+
+        timer = new Timer();
+        timer.schedule(new OutStandingARPHandler(), 4000, 4000);
+
+        /* ARP Refresh Timer to go off every 5 seconds to implement ARP aging */
+        arp_refresh_timer = new Timer();
+        arp_refresh_timer.schedule(new ARPRefreshHandler(), 5000, 5000);
+        logger.info("startUp: Caches created, timers started");
+    }
+
+    @SuppressWarnings("deprecation")
+       private void allocateCache() {
+        if (this.clusterContainerService == null) {
+            logger
+                    .error("un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+        logger.info("Creating Cache for HostTracker");
+        try {
+            this.clusterContainerService.createCache("hostTrackerAH", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            this.clusterContainerService.createCache("hostTrackerIH", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheConfigException cce) {
+            logger
+                    .error("Cache couldn't be created for HostTracker -  check cache mode");
+        } catch (CacheExistException cce) {
+            logger
+                    .error("Cache for HostTracker already exists, destroy and recreate");
+        }
+        logger.info("Cache successfully created for HostTracker");
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    private void retrieveCache() {
+        if (this.clusterContainerService == null) {
+            logger
+                    .error("un-initialized clusterContainerService, can't retrieve cache");
+            return;
+        }
+        logger.info("Retrieving cache for HostTrackerAH");
+        hostsDB = (ConcurrentMap<InetAddress, HostNodeConnector>) this.clusterContainerService
+                .getCache("hostTrackerAH");
+        if (hostsDB == null) {
+            logger.error("Cache couldn't be retrieved for HostTracker");
+        }
+        logger.info("Cache was successfully retrieved for HostTracker");
+        logger.info("Retrieving cache for HostTrackerIH");
+        inactiveStaticHosts = (ConcurrentMap<NodeConnector, HostNodeConnector>) this.clusterContainerService
+                .getCache("hostTrackerIH");
+        if (hostsDB == null) {
+            logger.error("Cache couldn't be retrieved for HostTrackerIH");
+        }
+        logger.info("Cache was successfully retrieved for HostTrackerIH");
+    }
+
+    public void nonClusterObjectCreate() {
+        hostsDB = new ConcurrentHashMap<InetAddress, HostNodeConnector>();
+        inactiveStaticHosts = new ConcurrentHashMap<NodeConnector, HostNodeConnector>();
+    }
+
+    @SuppressWarnings("deprecation")
+       private void destroyCache() {
+        if (this.clusterContainerService == null) {
+            logger.error("un-initialized clusterMger, can't destroy cache");
+            return;
+        }
+        logger.info("Destroying Cache for HostTracker");
+        this.clusterContainerService.destroyCache("hostTrackerAH");
+        this.clusterContainerService.destroyCache("hostTrackerIH");
+        nonClusterObjectCreate();
+    }
+
+    public void shutDown() {
+    }
+
+    public void setnewHostNotify(IfNewHostNotify obj) {
+        this.newHostNotify.add(obj);
+    }
+
+    public void unsetnewHostNotify(IfNewHostNotify obj) {
+        this.newHostNotify.remove(obj);
+    }
+
+    public void setArpHandler(IHostFinder hostFinder) {
+        this.hostFinder = hostFinder;
+    }
+
+    public void unsetArpHandler(IHostFinder hostFinder) {
+        if (this.hostFinder == hostFinder) {
+            logger.info("Arp Handler Service removed!");
+            this.hostFinder = null;
+        }
+    }
+
+    public void setTopologyManager(ITopologyManager s) {
+        this.topologyManager = s;
+    }
+
+    public void unsetTopologyManager(ITopologyManager s) {
+        if (this.topologyManager == s) {
+            logger.info("Topology Manager Service removed!");
+            this.topologyManager = null;
+        }
+    }
+
+    private boolean hostExists(HostNodeConnector host) {
+        HostNodeConnector lhost = hostsDB.get(host.getNetworkAddress());
+        return host.equals(lhost);
+    }
+
+    private HostNodeConnector getHostFromOnActiveDB(InetAddress networkAddress) {
+        return hostsDB.get(networkAddress);
+    }
+
+    private Entry<NodeConnector, HostNodeConnector> getHostFromInactiveDB(
+            InetAddress networkAddress) {
+        for (Entry<NodeConnector, HostNodeConnector> entry : inactiveStaticHosts
+                .entrySet()) {
+            if (entry.getValue().equalsByIP(networkAddress)) {
+                logger
+                        .debug(
+                                "getHostFromInactiveDB(): Inactive Host found for IP:{} ",
+                                networkAddress.getHostAddress());
+                return entry;
+            }
+        }
+        logger.debug(
+                "getHostFromInactiveDB() Inactive Host Not found for IP: {}",
+                networkAddress.getHostAddress());
+        return null;
+    }
+
+    private void removeHostFromInactiveDB(InetAddress networkAddress) {
+        NodeConnector nodeConnector = null;
+        for (Entry<NodeConnector, HostNodeConnector> entry : inactiveStaticHosts
+                .entrySet()) {
+            if (entry.getValue().equalsByIP(networkAddress)) {
+                nodeConnector = entry.getKey();
+                break;
+            }
+        }
+        if (nodeConnector != null) {
+            inactiveStaticHosts.remove(nodeConnector);
+            logger.debug("removeHostFromInactiveDB(): Host Removed for IP: {}",
+                    networkAddress.getHostAddress());
+            return;
+        }
+        logger.debug("removeHostFromInactiveDB(): Host Not found for IP: {}",
+                networkAddress.getHostAddress());
+    }
+
+    protected boolean hostMoved(HostNodeConnector host) {
+        if (hostQuery(host.getNetworkAddress()) != null) {
+            return true;
+        }
+        return false;
+    }
+
+    public HostNodeConnector hostQuery(InetAddress networkAddress) {
+        return hostsDB.get(networkAddress);
+    }
+
+    public Future<HostNodeConnector> discoverHost(InetAddress networkAddress) {
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        if (executor == null) {
+            logger.error("discoverHost: Null executor");
+            return null;
+        }
+        Callable<HostNodeConnector> worker = new HostTrackerCallable(this,
+                networkAddress);
+        Future<HostNodeConnector> submit = executor.submit(worker);
+        return submit;
+    }
+
+    public HostNodeConnector hostFind(InetAddress networkAddress) {
+        /*
+         * Sometimes at boot with containers configured in the startup
+         * we hit this path (from TIF) when hostFinder has not been set yet
+         * Caller already handles the null return
+         */
+
+        if (hostFinder == null) {
+            logger.info("Exiting hostFind, null hostFinder");
+            return null;
+        }
+
+        HostNodeConnector host = hostQuery(networkAddress);
+        if (host != null) {
+            logger.debug("hostFind(): Host found for IP: {}", networkAddress
+                    .getHostAddress());
+            return host;
+        }
+        /* host is not found, initiate a discovery */
+        hostFinder.find(networkAddress);
+        /* Also add this host to ARPPending List for any potential retries */
+        AddtoARPPendingList(networkAddress);
+        logger
+                .debug(
+                        "hostFind(): Host Not Found for IP: {}, Inititated Host Discovery ...",
+                        networkAddress.getHostAddress());
+        return null;
+    }
+
+    public Set<HostNodeConnector> getAllHosts() {
+        Set<HostNodeConnector> allHosts = new HashSet<HostNodeConnector>();
+        for (Entry<InetAddress, HostNodeConnector> entry : hostsDB.entrySet()) {
+            HostNodeConnector host = entry.getValue();
+            allHosts.add(host);
+        }
+        logger.debug("Exiting getAllHosts, Found {} Hosts", allHosts.size());
+        return allHosts;
+    }
+
+    @Override
+    public Set<HostNodeConnector> getActiveStaticHosts() {
+        Set<HostNodeConnector> list = new HashSet<HostNodeConnector>();
+        for (Entry<InetAddress, HostNodeConnector> entry : hostsDB.entrySet()) {
+            HostNodeConnector host = entry.getValue();
+            if (host.isStaticHost()) {
+                list.add(host);
+            }
+        }
+        logger.debug("getActiveStaticHosts(): Found {} Hosts", list.size());
+        return list;
+    }
+
+    @Override
+    public Set<HostNodeConnector> getInactiveStaticHosts() {
+        Set<HostNodeConnector> list = new HashSet<HostNodeConnector>();
+        for (Entry<NodeConnector, HostNodeConnector> entry : inactiveStaticHosts
+                .entrySet()) {
+            list.add(entry.getValue());
+        }
+        logger.debug("getInactiveStaticHosts(): Found {} Hosts", list.size());
+        return list;
+    }
+
+    private void AddtoARPPendingList(InetAddress networkAddr) {
+        ARPPending arphost = new ARPPending();
+
+        arphost.setHostIP(networkAddr);
+        arphost.setSent_count((short) 1);
+        ARPPendingList.add(arphost);
+        logger.debug("Host Added to ARPPending List, IP: {}", networkAddr
+                .toString());
+    }
+
+    private void removePendingARPFromList(int index) {
+        if (index >= ARPPendingList.size()) {
+            logger
+                    .warn(
+                            "removePendingARPFromList(): index greater than the List. Size:{}, Index:{}",
+                            ARPPendingList.size(), index);
+            return;
+        }
+        ARPPending arphost = ARPPendingList.remove(index);
+        HostTrackerCallable htCallable = arphost.getHostTrackerCallable();
+        if (htCallable != null)
+            htCallable.wakeup();
+    }
+
+    public void setCallableOnPendingARP(InetAddress networkAddr,
+            HostTrackerCallable callable) {
+        ARPPending arphost;
+        for (int i = 0; i < ARPPendingList.size(); i++) {
+            arphost = ARPPendingList.get(i);
+            if (arphost.getHostIP().equals(networkAddr)) {
+                arphost.setHostTrackerCallable(callable);
+            }
+        }
+    }
+
+    private void ProcPendingARPReqs(InetAddress networkAddr) {
+        ARPPending arphost;
+
+        for (int i = 0; i < ARPPendingList.size(); i++) {
+            arphost = ARPPendingList.get(i);
+            if (arphost.getHostIP().equals(networkAddr)) {
+                /* An ARP was sent for this host. The address is learned,
+                 * remove the request
+                 */
+                removePendingARPFromList(i);
+                logger.debug("Host Removed from ARPPending List, IP: {}",
+                        networkAddr.toString());
+                return;
+            }
+        }
+
+        /*
+         * It could have been a host from the FailedARPReqList
+         */
+
+        for (int i = 0; i < failedARPReqList.size(); i++) {
+            arphost = failedARPReqList.get(i);
+            if (arphost.getHostIP().equals(networkAddr)) {
+                /* An ARP was sent for this host. The address is learned,
+                 * remove the request
+                 */
+                failedARPReqList.remove(i);
+                logger.debug("Host Removed from FailedARPReqList List, IP: {}",
+                        networkAddr.toString());
+                return;
+            }
+        }
+    }
+
+    // Learn a new Host
+    private void learnNewHost(HostNodeConnector host) {
+        host.initArpSendCountDown();
+        hostsDB.put(host.getNetworkAddress(), host);
+        logger.debug("New Host Learned: MAC: {}  IP: {}", HexEncode
+                .bytesToHexString(host.getDataLayerAddressBytes()), host
+                .getNetworkAddress().getHostAddress());
+    }
+
+    // Remove known Host
+    private void removeKnownHost(InetAddress key) {
+        HostNodeConnector host = hostsDB.get(key);
+        if (host != null) {
+            logger.debug("Removing Host: IP:{}", host.getNetworkAddress()
+                    .getHostAddress());
+            hostsDB.remove(key);
+        } else {
+            logger
+                    .error(
+                            "removeKnownHost(): Host for IP address {} not found in hostsDB",
+                            key.getHostAddress());
+        }
+    }
+
+    private class NotifyHostThread extends Thread {
+
+        private HostNodeConnector host;
+
+        public NotifyHostThread(HostNodeConnector h) {
+            this.host = h;
+        }
+
+        public void run() {
+            /* Check for Host Move case */
+            if (hostMoved(host)) {
+                /*
+                 * Host has been moved from one location (switch,port, MAC, or VLAN).
+                 * Remove the existing host with its previous location parameters,
+                 * inform the applications, and add it as a new Host
+                 */
+                HostNodeConnector removedHost = hostsDB.get(host
+                        .getNetworkAddress());
+                removeKnownHost(host.getNetworkAddress());
+                if (removedHost != null) {
+                    notifyHostLearnedOrRemoved(removedHost, false);
+                    logger.debug(
+                            "Host move occurred. Old Host:{}, New Host: {}",
+                            removedHost, host);
+                } else {
+                    logger.error(
+                            "Host to be removed not found in hostsDB. Host {}",
+                            removedHost);
+                }
+            }
+
+            /* check if there is an outstanding request for this host */
+            InetAddress networkAddr = host.getNetworkAddress();
+
+            // add and notify
+            learnNewHost(host);
+            ProcPendingARPReqs(networkAddr);
+            notifyHostLearnedOrRemoved(host, true);
+        }
+    }
+
+    public void hostListener(HostNodeConnector host) {
+
+        if (hostExists(host)) {
+            logger.debug("ARP received for Host: {}", host);
+            HostNodeConnector existinghost = hostsDB.get(host
+                    .getNetworkAddress());
+            existinghost.initArpSendCountDown();
+            return;
+        }
+        new NotifyHostThread(host).start();
+    }
+
+    // Notify whoever is interested that a new host was learned (dynamically or statically)
+    private void notifyHostLearnedOrRemoved(HostNodeConnector host, boolean add) {
+        // Update listeners if any
+        if (newHostNotify != null) {
+            synchronized (this.newHostNotify) {
+                for (IfNewHostNotify ta : newHostNotify) {
+                    try {
+                        if (add) {
+                            ta.notifyHTClient(host);
+                        } else {
+                            ta.notifyHTClientHostRemoved(host);
+                        }
+                    } catch (Exception e) {
+                        logger.error("Exception on callback", e);
+                    }
+                }
+            }
+        } else {
+            logger
+                    .error("notifyHostLearnedOrRemoved(): New host notify is null");
+        }
+
+        // Topology update is for some reason outside of listeners registry logic
+        Node node = host.getnodeconnectorNode();
+        Host h = null;
+        NodeConnector p = host.getnodeConnector();
+        try {
+            DataLinkAddress dla = new EthernetAddress(host
+                    .getDataLayerAddressBytes());
+            h = new org.opendaylight.controller.sal.core.Host(dla, host
+                    .getNetworkAddress());
+        } catch (ConstructionException ce) {
+            p = null;
+            h = null;
+        }
+
+        if (topologyManager != null && p != null && h != null) {
+            if (add == true) {
+                Tier tier = new Tier(1);
+                switchManager.setNodeProp(node, tier);
+                topologyManager.updateHostLink(p, h, UpdateType.ADDED, null);
+                /*
+                 * This is a temporary fix for Cisco Live's Hadoop Demonstration.
+                 * The concept of Tiering must be revisited based on other application requirements
+                 * and the design might warrant a separate module (as it involves tracking the topology/
+                 * host changes & updating the Tiering numbers in an effective manner).
+                 */
+                updateSwitchTiers(node, 1);
+
+                /*
+                 * The following 2 lines are added for testing purposes.
+                 * We can remove it once the North-Bound APIs are available for testing.
+
+                ArrayList<ArrayList<String>> hierarchies = getHostNetworkHierarchy(host.getNetworkAddress());
+                logHierarchies(hierarchies);
+                 */
+            } else {
+                // No need to reset the tiering if no other hosts are currently connected
+                // If this switch was discovered to be an access switch, it still is even if the host is down
+                Tier tier = new Tier(0);
+                switchManager.setNodeProp(node, tier);
+                topologyManager.updateHostLink(p, h, UpdateType.REMOVED, null);
+            }
+        }
+    }
+
+    /**
+     * When a new Host is learnt by the hosttracker module, it places the directly connected Node
+     * in Tier-1 & using this function, updates the Tier value for all other Nodes in the network
+     * hierarchy.
+     *
+     * This is a recursive function and it takes care of updating the Tier value for all the connected
+     * and eligible Nodes.
+     *
+     * @param n        Node that represents one of the Vertex in the Topology Graph.
+     * @param currentTier The Tier on which n belongs
+     */
+    private void updateSwitchTiers(Node n, int currentTier) {
+        Map<Node, Set<Edge>> ndlinks = topologyManager.getNodeEdges();
+        if (ndlinks == null) {
+            logger.debug(
+                    "updateSwitchTiers(): ndlinks null for Node: {}, Tier:{}",
+                    n, currentTier);
+            return;
+        }
+        Set<Edge> links = ndlinks.get(n);
+        if (links == null) {
+            logger.debug("updateSwitchTiers(): links null for ndlinks:{}",
+                    ndlinks);
+            return;
+        }
+        ArrayList<Node> needsVisiting = new ArrayList<Node>();
+        for (Edge lt : links) {
+            if (!lt.getHeadNodeConnector().getType().equals(
+                    NodeConnector.NodeConnectorIDType.OPENFLOW)) {
+                // We don't want to work on Node that are not openflow
+                // for now
+                continue;
+            }
+            Node dstNode = lt.getHeadNodeConnector().getNode();
+            if (switchNeedsTieringUpdate(dstNode, currentTier + 1)) {
+                Tier t = new Tier(currentTier + 1);
+                switchManager.setNodeProp(dstNode, t);
+                //logger.info("Updating Switch Tier "+ (currentTier+1) +" for "+String.format("%x", dstSw.getId()));
+                needsVisiting.add(dstNode);
+            }
+        }
+
+        /*
+         * Due to the nature of the problem, having a separate loop for nodes
+         * that needs visiting provides a decent walk optimization.
+         */
+        for (Node node : needsVisiting) {
+            updateSwitchTiers(node, currentTier + 1);
+        }
+    }
+
+    /**
+     * Internal convenience routine to check the eligibility of a Switch for a Tier update.
+     * Any Node with Tier=0 or a Tier value that is greater than the new Tier Value is eligible
+     * for the update.
+     *
+     * @param n Node for which the Tier update eligibility is checked
+     * @param tier new Tier Value
+     * @return <code>true</code> if the Node is eligible for Tier Update
+     *         <code>false</code> otherwise
+     */
+
+    private boolean switchNeedsTieringUpdate(Node n, int tier) {
+        if (n == null) {
+            logger.error("switchNeedsTieringUpdate(): Null node for tier: {}",
+                    tier);
+            return false;
+        }
+        /*
+         * Node could have gone down
+         */
+        if (!switchManager.getNodes().contains(n)) {
+            return false;
+        }
+        // This is the case where Tier was never set for this node
+        Tier t = (Tier) switchManager.getNodeProp(n, Tier.TierPropName);
+        if (t == null)
+            return true;
+        if (t.getValue() == 0)
+            return true;
+        else if (t.getValue() > tier)
+            return true;
+        //logger.info(getContainerName()+" -> "+ "Switch "+String.format("%x", sw.getId())+ " is in better Tier "+sw.getTier()+" ... skipping "+tier);
+        return false;
+    }
+
+    /**
+     * Internal convenience routine to clear all the Tier values to 0.
+     * This cleanup is performed during cases such as Topology Change where the existing Tier values
+     * might become incorrect
+     */
+    private void clearTiers() {
+        Set<Node> nodes = null;
+        if (switchManager == null) {
+            logger.error("clearTiers(): Null switchManager");
+            return;
+        }
+        nodes = switchManager.getNodes();
+
+        for (Node n : nodes) {
+            Tier t = new Tier(0);
+            switchManager.setNodeProp(n, t);
+        }
+    }
+
+    /**
+     * Internal convenience routine to print the hierarchies of switches.
+     */
+    @SuppressWarnings("unused")
+    private void logHierarchies(ArrayList<ArrayList<String>> hierarchies) {
+        String hierarchyString = null;
+        int num = 1;
+        for (ArrayList<String> hierarchy : hierarchies) {
+            StringBuffer buf = new StringBuffer();
+            buf.append("Hierarchy#" + num + " : ");
+            for (String switchName : hierarchy) {
+                buf.append(switchName + "/");
+            }
+            logger.debug(getContainerName() + " -> " + buf.toString());
+            num++;
+        }
+    }
+
+    /**
+     * getHostNetworkHierarchy is the Back-end routine for the North-Bound API that returns
+     * the Network Hierarchy for a given Host. This API is typically used by applications like
+     * Hadoop for Rack Awareness functionality.
+     *
+     * @param hostAddress IP-Address of the host/node.
+     * @return Network Hierarchies represented by an Array of Array (of Switch-Ids as String).
+     */
+    public List<List<String>> getHostNetworkHierarchy(InetAddress hostAddress) {
+        HostNodeConnector host = hostQuery(hostAddress);
+        if (host == null)
+            return null;
+
+        List<List<String>> hierarchies = new ArrayList<List<String>>();
+        ArrayList<String> currHierarchy = new ArrayList<String>();
+        hierarchies.add(currHierarchy);
+
+        Node node = host.getnodeconnectorNode();
+        updateCurrentHierarchy(node, currHierarchy, hierarchies);
+        return hierarchies;
+    }
+
+    /**
+     * dpidToHostNameHack is a hack function for Cisco Live Hadoop Demo.
+     * Mininet is used as the network for Hadoop Demos & in order to give a meaningful
+     * rack-awareness switch names, the DPID is organized in ASCII Characters and
+     * retrieved as string.
+     *
+     * @param dpid Switch DataPath Id
+     * @return Ascii String represented by the DPID.
+     */
+    private String dpidToHostNameHack(long dpid) {
+        String hex = Long.toHexString(dpid);
+
+        StringBuffer sb = new StringBuffer();
+        int result = 0;
+        for (int i = 0; i < hex.length(); i++) {
+            result = (int) ((dpid >> (i * 8)) & 0xff);
+            if (result == 0)
+                continue;
+            if (result < 0x30)
+                result += 0x40;
+            sb.append(String.format("%c", result));
+        }
+        return sb.reverse().toString();
+    }
+
+    /**
+     * A convenient recursive routine to obtain the Hierarchy of Switches.
+     *
+     * @param node Current Node in the Recursive routine.
+     * @param currHierarchy Array of Nodes that make this hierarchy on which the Current Switch belong
+     * @param fullHierarchy Array of multiple Hierarchies that represent a given host.
+     */
+    @SuppressWarnings("unchecked")
+    private void updateCurrentHierarchy(Node node,
+            ArrayList<String> currHierarchy, List<List<String>> fullHierarchy) {
+        //currHierarchy.add(String.format("%x", currSw.getId()));
+        currHierarchy.add(dpidToHostNameHack((Long) node.getID()));
+        ArrayList<String> currHierarchyClone = (ArrayList<String>) currHierarchy
+                .clone(); //Shallow copy as required
+
+        Map<Node, Set<Edge>> ndlinks = topologyManager.getNodeEdges();
+        if (ndlinks == null) {
+            logger
+                    .debug(
+                            "updateCurrentHierarchy(): topologyManager returned null ndlinks for node: {}",
+                            node);
+            return;
+        }
+        Node n = NodeCreator.createOFNode((Long) node.getID());
+        Set<Edge> links = ndlinks.get(n);
+        if (links == null) {
+            logger.debug("updateCurrentHierarchy(): Null links for ndlinks");
+            return;
+        }
+        for (Edge lt : links) {
+            if (!lt.getHeadNodeConnector().getType().equals(
+                    NodeConnector.NodeConnectorIDType.OPENFLOW)) {
+                // We don't want to work on Node that are not openflow
+                // for now
+                continue;
+            }
+            Node dstNode = lt.getHeadNodeConnector().getNode();
+
+            Tier nodeTier = (Tier) switchManager.getNodeProp(node,
+                    Tier.TierPropName);
+            Tier dstNodeTier = (Tier) switchManager.getNodeProp(dstNode,
+                    Tier.TierPropName);
+            if (dstNodeTier.getValue() > nodeTier.getValue()) {
+                ArrayList<String> buildHierarchy = currHierarchy;
+                if (currHierarchy.size() > currHierarchyClone.size()) {
+                    buildHierarchy = (ArrayList<String>) currHierarchyClone
+                            .clone(); //Shallow copy as required
+                    fullHierarchy.add(buildHierarchy);
+                }
+                updateCurrentHierarchy(dstNode, buildHierarchy, fullHierarchy);
+            }
+        }
+    }
+
+    @Override
+    public void edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
+        Long srcNid = null;
+        Short srcPort = null;
+        Long dstNid = null;
+        Short dstPort = null;
+        boolean added = false;
+        String srcType = null;
+        String dstType = null;
+
+        if (e == null || type == null) {
+            logger.error("Edge or Update type are null!");
+            return;
+        } else {
+            srcType = e.getTailNodeConnector().getType();
+            dstType = e.getHeadNodeConnector().getType();
+
+            if (srcType.equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+                logger.debug("Skip updates for {}", e);
+                return;
+            }
+
+            if (!srcType.equals(NodeConnector.NodeConnectorIDType.OPENFLOW)) {
+                logger.error("For now we cannot handle updates for "
+                        + "non-openflow nodes");
+                return;
+            }
+
+            if (dstType.equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+                logger.debug("Skip updates for {}", e);
+                return;
+            }
+
+            if (!dstType.equals(NodeConnector.NodeConnectorIDType.OPENFLOW)) {
+                logger.error("For now we cannot handle updates for "
+                        + "non-openflow nodes");
+                return;
+            }
+
+            // At this point we know we got an openflow update, so
+            // lets fill everything accordingly.
+            srcNid = (Long) e.getTailNodeConnector().getNode()
+                    .getID();
+            srcPort = (Short) e.getTailNodeConnector().getID();
+            dstNid = (Long) e.getHeadNodeConnector().getNode()
+                    .getID();
+            dstPort = (Short) e.getHeadNodeConnector().getID();
+
+            // Now lets update the added flag
+            switch (type) {
+            case ADDED:
+            case CHANGED:
+                added = true;
+                break;
+            case REMOVED:
+                added = false;
+            }
+        }
+
+        logger.debug("HostTracker Topology linkUpdate handling src:{}[port {}] dst:{}[port {}] added: {}",
+                     new Object[] { srcNid, srcPort, dstNid, dstPort, added });
+        clearTiers();
+        for (Entry<InetAddress, HostNodeConnector> entry : hostsDB.entrySet()) {
+            HostNodeConnector host = entry.getValue();
+            Node node = host.getnodeconnectorNode();
+            if (node != null) {
+                Tier t = new Tier(1);
+                switchManager.setNodeProp(node, t);
+                updateSwitchTiers(node, 1);
+            }
+        }
+    }
+
+    public void subnetNotify(Subnet sub, boolean add) {
+        logger.debug("Received subnet notification: {}  add={}", sub, add);
+        if (add) {
+            for (int i = 0; i < failedARPReqList.size(); i++) {
+                ARPPending arphost;
+                arphost = failedARPReqList.get(i);
+                logger.debug(
+                        "Sending the ARP from FailedARPReqList fors IP: {}",
+                        arphost.getHostIP().getHostAddress());
+                hostFinder.find(arphost.getHostIP());
+            }
+        }
+    }
+
+    class OutStandingARPHandler extends TimerTask {
+        public void run() {
+            ARPPending arphost;
+            /* This routine runs every 4 seconds */
+            // logger.info ("ARP Handler called");
+            for (int i = 0; i < ARPPendingList.size(); i++) {
+                arphost = ARPPendingList.get(i);
+                if (arphost.getSent_count() < switchManager.getHostRetryCount()) {
+                    /* No reply has been received of first ARP Req, send the next one */
+                    hostFinder.find(arphost.getHostIP());
+                    arphost.sent_count++;
+                    logger.debug("ARP Sent from ARPPending List, IP: {}",
+                            arphost.getHostIP().getHostAddress());
+                } else if (arphost.getSent_count() >= switchManager
+                        .getHostRetryCount()) {
+                    /* Two ARP requests have been sent without
+                     * receiving a reply, remove this from the
+                     * pending list
+                     */
+                    removePendingARPFromList(i);
+                    logger
+                            .debug(
+                                    "ARP reply not received after two attempts, removing from Pending List IP: {}",
+                                    arphost.getHostIP().getHostAddress());
+                    /*
+                     * Add this host to a different list which will be processed on link
+                     * up events
+                     */
+                    logger.debug("Adding the host to FailedARPReqList IP: {}",
+                            arphost.getHostIP().getHostAddress());
+                    failedARPReqList.add(arphost);
+
+                } else {
+                    logger
+                            .error(
+                                    "Inavlid arp_sent count for entery at index: {}",
+                                    i);
+                }
+            }
+        }
+    }
+
+    private class ARPRefreshHandler extends TimerTask {
+        public void run() {
+            if ((clusterContainerService != null)
+                    && !clusterContainerService.amICoordinator()) {
+                return;
+            }
+            if ((switchManager != null)
+                    && !switchManager.isHostRefreshEnabled()) {
+                /*
+                 * The host probe procedure was disabled by CLI
+                 */
+                return;
+            }
+            if (hostsDB == null) {
+                /* hostsDB is not allocated yet */
+                logger
+                        .error("ARPRefreshHandler(): hostsDB is not allocated yet:");
+                return;
+            }
+            for (Entry<InetAddress, HostNodeConnector> entry : hostsDB
+                    .entrySet()) {
+                HostNodeConnector host = entry.getValue();
+                if (host.isStaticHost()) {
+                    /* this host was learned via API3, don't age it out */
+                    continue;
+                }
+
+                short arp_cntdown = host.getArpSendCountDown();
+                arp_cntdown--;
+                if (arp_cntdown > switchManager.getHostRetryCount()) {
+                    host.setArpSendCountDown(arp_cntdown);
+                } else if (arp_cntdown <= 0) {
+                    /* No ARP Reply received in last 2 minutes, remove this host and inform applications*/
+                    removeKnownHost(entry.getKey());
+                    notifyHostLearnedOrRemoved(host, false);
+                } else if (arp_cntdown <= switchManager.getHostRetryCount()) {
+                    /* Use the services of arphandler to check if host is still there */
+                    // logger.info("Probe for Host:{}", host);
+                    //logger.info("ARP Probing ("+arp_cntdown+") for "+host.toString());
+                    logger.trace("ARP Probing ({}) for {}({})", new Object[] {
+                            arp_cntdown,
+                            host.getNetworkAddress().getHostAddress(),
+                            HexEncode.bytesToHexString(host
+                                    .getDataLayerAddressBytes()) });
+                    host.setArpSendCountDown(arp_cntdown);
+                    hostFinder.probe(host);
+                }
+            }
+        }
+    }
+
+    /**
+     * Inform the controller IP to MAC binding of a host and its
+     * connectivity to an openflow switch in terms of Node, port, and
+     * VLAN.
+     *
+     * @param networkAddr   IP address of the host
+     * @param dataLayer                Address MAC address of the host
+     * @param nc            NodeConnector to which host is connected
+     * @param port          Port of the switch to which host is connected
+     * @param vlan          Vlan of which this host is member of
+     *
+     * @return Status          The status object as described in {@code Status}
+     *                                                 indicating the result of this action.
+     */
+
+    public Status addStaticHostReq(InetAddress networkAddr,
+            byte[] dataLayerAddress, NodeConnector nc, short vlan) {
+        if (dataLayerAddress.length != 6) {
+               return new Status(StatusCode.BADREQUEST, "Invalid MAC address");
+        }
+
+        HostNodeConnector host = null;
+        try {
+            host = new HostNodeConnector(dataLayerAddress, networkAddr, nc,
+                                         vlan);
+            if (hostExists(host)) {
+                // This host is already learned either via ARP or through a northbound request
+                HostNodeConnector transHost = hostsDB.get(networkAddr);
+                transHost.setStaticHost(true);
+                return new Status(StatusCode.SUCCESS, null);
+            }
+            host.setStaticHost(true);
+            /*
+             * Before adding host, Check if the switch and the port have already come up
+             */
+            if (switchManager.isNodeConnectorEnabled(nc)) {
+                learnNewHost(host);
+                notifyHostLearnedOrRemoved(host, true);
+            } else {
+                inactiveStaticHosts.put(nc, host);
+                logger
+                        .debug(
+                                "Switch or switchport is not up, adding host {} to inactive list",
+                                networkAddr.getHostName());
+            }
+            return new Status(StatusCode.SUCCESS, null);
+        } catch (ConstructionException e) {
+            return new Status(StatusCode.INTERNALERROR, "Host could not be created");
+        }
+
+    }
+
+    /**
+     * Update the controller IP to MAC binding of a host and its
+     * connectivity to an openflow switch in terms of
+     * switch id, switch port, and VLAN.
+     *
+     * @param networkAddr   IP address of the host
+     * @param dataLayer                Address MAC address of the host
+     * @param nc            NodeConnector to which host is connected
+     * @param port          Port of the switch to which host is connected
+     * @param vlan          Vlan of which this host is member of
+     *
+     * @return boolean         true if the host was added successfully,
+     * false otherwise
+     */
+    public boolean updateHostReq(InetAddress networkAddr,
+                                 byte[] dataLayerAddress, NodeConnector nc,
+                                 short vlan) {
+        if (nc == null) {
+            return false;
+        }
+        HostNodeConnector host = null;
+        try {
+            host = new HostNodeConnector(dataLayerAddress, networkAddr, nc,
+                                         vlan);
+            if (!hostExists(host)) {
+                if ((inactiveStaticHosts.get(nc)) != null) {
+                    inactiveStaticHosts.replace(nc, host);
+                    return true;
+                }
+                return false;
+            }
+            hostsDB.replace(networkAddr, host);
+            return true;
+        } catch (ConstructionException e) {
+        }
+        return false;
+    }
+
+    /**
+     * Remove from the controller IP to MAC binding of a host and its
+     * connectivity to an openflow switch
+     *
+     * @param networkAddr   IP address of the host
+     *
+     * @return boolean         true if the host was removed successfully,
+     * false otherwise
+     */
+
+    public Status removeStaticHostReq(InetAddress networkAddress) {
+        // Check if host is in active hosts database
+        HostNodeConnector host = getHostFromOnActiveDB(networkAddress);
+        if (host != null) {
+            // Validation check
+            if (!host.isStaticHost()) {
+               return new Status(StatusCode.FORBIDDEN,
+                               "Host " + networkAddress.getHostName() + 
+                               " is not static");
+            }
+            // Remove and notify
+            notifyHostLearnedOrRemoved(host, false);
+            removeKnownHost(networkAddress);
+            return new Status(StatusCode.SUCCESS, null);
+        }
+
+        // Check if host is in inactive hosts database
+        Entry<NodeConnector, HostNodeConnector> entry = getHostFromInactiveDB(networkAddress);
+        if (entry != null) {
+            host = entry.getValue();
+            // Validation check
+            if (!host.isStaticHost()) {
+               return new Status(StatusCode.FORBIDDEN,
+                               "Host " + networkAddress.getHostName() + 
+                               " is not static");
+            }
+            this.removeHostFromInactiveDB(networkAddress);
+            return new Status(StatusCode.SUCCESS, null);
+        }
+
+        // Host is neither in active nor inactive hosts database
+        return new Status(StatusCode.NOTFOUND, "Host does not exist");
+    }
+
+    @Override
+    public void modeChangeNotify(Node node, boolean proactive) {
+        logger.debug("Set Switch {} Mode to {}", node.getID(), proactive);
+    }
+
+    @Override
+    public void notifyNode(Node node, UpdateType type,
+            Map<String, Property> propMap) {
+        if (node == null)
+            return;
+
+        switch (type) {
+        case REMOVED:
+            long sid = (Long) node.getID();
+            logger.debug("Received removedSwitch for sw id {}", HexEncode
+                    .longToHexString(sid));
+            for (Entry<InetAddress, HostNodeConnector> entry : hostsDB
+                    .entrySet()) {
+                HostNodeConnector host = entry.getValue();
+                if (host.getnodeconnectornodeId() == sid) {
+                    logger.debug("Switch: {} is down, remove from Hosts_DB",
+                            sid);
+                    removeKnownHost(entry.getKey());
+                    notifyHostLearnedOrRemoved(host, false);
+                }
+            }
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void notifyNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Map<String, Property> propMap) {
+        if (nodeConnector == null)
+            return;
+
+        boolean up = false;
+        switch (type) {
+        case ADDED:
+            up = true;
+            break;
+        case REMOVED:
+            break;
+        case CHANGED:
+            State state = (State) propMap.get(State.StatePropName);
+            if ((state != null) && (state.getValue() == State.EDGE_UP)) {
+                up = true;
+            }
+            break;
+        default:
+            return;
+        }
+
+        if (up) {
+            handleNodeConnectorStatusUp(nodeConnector);
+        } else {
+            handleNodeConnectorStatusDown(nodeConnector);
+        }
+    }
+
+    @Override
+    public Status addStaticHost(String networkAddress, String dataLayerAddress,
+                                NodeConnector nc, String vlan) {
+        try {
+            InetAddress ip = InetAddress.getByName(networkAddress);
+            if (nc == null) {
+               return new Status(StatusCode.BADREQUEST, "Invalid NodeId");
+            }
+            return addStaticHostReq(ip,
+                                    HexEncode
+                                    .bytesFromHexString(dataLayerAddress),
+                                    nc,
+                    Short.valueOf(vlan));
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            return new Status(StatusCode.BADREQUEST, "Invalid Address");
+        }
+    }
+
+    @Override
+    public Status removeStaticHost(String networkAddress) {
+        InetAddress address;
+        try {
+            address = InetAddress.getByName(networkAddress);
+            return removeStaticHostReq(address);
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            return new Status(StatusCode.BADREQUEST, "Invalid Address");
+        }
+    }
+
+    private void handleNodeConnectorStatusUp(NodeConnector nodeConnector) {
+        ARPPending arphost;
+
+        logger.info("handleNodeConnectorStatusUp {}", nodeConnector);
+
+        for (int i = 0; i < failedARPReqList.size(); i++) {
+            arphost = failedARPReqList.get(i);
+            logger.debug("Sending the ARP from FailedARPReqList fors IP: {}",
+                    arphost.getHostIP().getHostAddress());
+            hostFinder.find(arphost.getHostIP());
+        }
+        HostNodeConnector host = inactiveStaticHosts.get(nodeConnector);
+        if (host != null) {
+            inactiveStaticHosts.remove(nodeConnector);
+            learnNewHost(host);
+            notifyHostLearnedOrRemoved(host, true);
+        }
+    }
+
+    private void handleNodeConnectorStatusDown(NodeConnector nodeConnector) {
+        long sid = (Long) nodeConnector.getNode().getID();
+        short port = (Short) nodeConnector.getID();
+
+        logger.debug("handleNodeConnectorStatusDown {}", nodeConnector);
+
+        for (Entry<InetAddress, HostNodeConnector> entry : hostsDB.entrySet()) {
+            HostNodeConnector host = entry.getValue();
+            if ((host.getnodeconnectornodeId() == sid)
+                    && (host.getnodeconnectorportId() == port)) {
+                logger.debug(
+                        "Switch: {}, Port: {} is down, remove from Hosts_DB",
+                        sid, port);
+                removeKnownHost(entry.getKey());
+                notifyHostLearnedOrRemoved(host, false);
+            }
+        }
+    }
+
+    void setClusterContainerService(IClusterContainerServices s) {
+        logger.debug("Cluster Service set");
+        this.clusterContainerService = s;
+    }
+
+    void unsetClusterContainerService(IClusterContainerServices s) {
+        if (this.clusterContainerService == s) {
+            logger.info("Cluster Service removed!");
+            this.clusterContainerService = null;
+        }
+    }
+
+    void setSwitchManager(ISwitchManager s) {
+        logger.info("SwitchManager set");
+        this.switchManager = s;
+    }
+
+    void unsetSwitchManager(ISwitchManager s) {
+        if (this.switchManager == s) {
+            logger.info("SwitchManager removed!");
+            this.switchManager = null;
+        }
+    }
+
+    public String getContainerName() {
+        if (containerName == null)
+            return GlobalConstants.DEFAULT.toString();
+        return containerName;
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init(Component c) {
+        Dictionary<?, ?> props = c.getServiceProperties();
+        if (props != null) {
+            this.containerName = (String) props.get("containerName");
+            logger.debug("Running containerName:" + this.containerName);
+        } else {
+            // In the Global instance case the containerName is empty
+            this.containerName = "";
+        }
+        startUp();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        destroyCache();
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    @Override
+    public void edgeOverUtilized(Edge edge) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void edgeUtilBackToNormal(Edge edge) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/HostTrackerCallable.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/HostTrackerCallable.java
new file mode 100644 (file)
index 0000000..f6f2c6d
--- /dev/null
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/*
+ * Provides a mechanism for applications to do an inline Host Discovery as opposed
+ * to a delayed discovery
+ */
+package org.opendaylight.controller.hosttracker;
+
+/**
+ * This Class provides methods to discover Host through a blocking call
+ * mechanism. Applications can make use of these methods if they don't 
+ * find a host in HostTracker's database and want to discover the host  
+ * in the same thread without being called by a callback function.
+ */
+import java.net.InetAddress;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+public class HostTrackerCallable implements Callable<HostNodeConnector> {
+
+    InetAddress trackedHost;
+    HostTracker hostTracker;
+    protected CountDownLatch latch;
+
+    public HostTrackerCallable(HostTracker tracker, InetAddress inet) {
+        trackedHost = inet;
+        hostTracker = tracker;
+        latch = new CountDownLatch(1);
+    }
+
+    @Override
+    public HostNodeConnector call() throws Exception {
+        HostNodeConnector h = hostTracker.hostFind(trackedHost);
+        if (h != null)
+            return h;
+        hostTracker.setCallableOnPendingARP(trackedHost, this);
+        latch.await();
+        return hostTracker.hostQuery(trackedHost);
+    }
+
+    public void wakeup() {
+        this.latch.countDown();
+    }
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java
new file mode 100644 (file)
index 0000000..cb15fb0
--- /dev/null
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+/**
+ * This interface defines the method to notify detected Host on the
+ * network. The information includes Host's IP address, MAC address,
+ * switch ID, port, and VLAN.
+ *
+ */
+
+public interface IfHostListener {
+    /**
+     * Learns  new Hosts. Called by ArpHandler and implemented in
+     * HostTracker.java. If a Host is learned for the first time then
+     * adds it to the local database and informs other applications
+     * of coming up a new Host. For the hosts which it has already
+     * learned, it refreshes them.
+     *
+     * @param host             Host info encapsulated in HostNodeConnector class
+     */
+    public void hostListener(HostNodeConnector host);
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java
new file mode 100644 (file)
index 0000000..554da42
--- /dev/null
@@ -0,0 +1,127 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * This interface defines the methods to retrieve information about
+ * learned Hosts. Also provides methods to statically add/remove
+ * Hosts from the local database.
+ *
+ */
+
+public interface IfIptoHost {
+    /**
+     * Applications call this interface methods to determine IP address to MAC binding and its
+     * connectivity to an OpenFlow switch in term of Node, Port, and VLAN. These
+     * bindings are learned dynamically as well as can be added statically through
+     * Northbound APIs. If a binding is unknown, then an ARP request is initiated
+     * immediately to discover the host.
+     *
+     * @param networkAddress   IP Address of the Host encapsulated in class InetAddress
+     * @return                                 {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *                                                         Class that contains the Host info such as its MAC address,
+     *                                                         Switch ID, port, VLAN. If Host is not found, returns NULL
+     */
+    public HostNodeConnector hostFind(InetAddress networkAddress);
+
+    /**
+     * Checks the local Host Database to see if a Host has been learned for a
+     * given IP address.
+     *
+     * @param networkAddress   IP Address of the Host encapsulated in class InetAddress
+     * @return                                 {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *                                                         Class that contains the Host info such as its MAC address,
+     *                                                         Switch ID, port, VLAN. If Host is not found, returns NULL
+     *
+     */
+    public HostNodeConnector hostQuery(InetAddress networkAddress);
+
+    /**
+     * Initiates an immediate discovery of the Host for a given IP address. This
+     * provides for the calling applications to block on the host discovery.
+     *
+     * @param networkAddress           IP address encapsulated in InetAddress class
+     * @return                                         Future {@link org.opendaylight.controller.hosttracker.HostTrackerCallable}
+     */
+    public Future<HostNodeConnector> discoverHost(InetAddress networkAddress);
+
+    /**
+     * Returns the Network Hierarchy for a given Host. This API is typically used by
+     * applications like Hadoop for Rack Awareness functionality.
+     *
+     * @param                                  IP address of the Host encapsulated in InetAddress class
+     * @return                                 List of String ArrayList containing the Hierarchies.
+     */
+    public List<List<String>> getHostNetworkHierarchy(InetAddress hostAddress);
+
+    /**
+     * Returns all the the Hosts either learned dynamically or added statically via
+     * Northbound APIs.
+     *
+     * @return                                 Set of {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}.
+     *                                                         Class that contains the Host info such as its MAC address,
+     *                                                         Switch ID, port, VLAN.
+     */
+    public Set<HostNodeConnector> getAllHosts();
+
+    /**
+     * Returns all the "Active Hosts" learned "Statically" via Northbound APIs. These Hosts
+     * are categorized as "Active" because the Switch and Port they are connected to, are in
+     * up state.
+     *
+     * @return                                 Set of {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}.
+     *                                                         Class that contains the Host info such as MAC address,
+     *                                                         Switch ID, port, VLAN. If Host is not found, returns NULL
+     */
+    public Set<HostNodeConnector> getActiveStaticHosts();
+
+    /**
+     * Returns all the "Inactive Hosts" learned "Statically" via Northbound APIs. These Hosts
+     * are categorized as "Inactive" because either the Switch or the Port they are connected
+     * to, is in down state.
+     *
+     * @return                                 Set of HostNodeConnector {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}.
+     *                                                         HostNodeConnector is Class that
+     *                                                         contains the Host info such as its MAC address, OpenFlowNode
+     *                                                         ID, port, VLAN.
+     */
+    public Set<HostNodeConnector> getInactiveStaticHosts();
+
+    /**
+     * Hosts can be learned dynamically or added statically. This method allows the addition
+     * of a Host to the local database statically.
+     *
+     * @param networkAddress           IP Address of the Host
+     * @param dataLayerAddress         MAC Address of the Host
+     * @param nc                                   NodeConnector to which the host is attached
+     * @param vlan                                     VLAN the host belongs to
+     * @return                                         The status object as described in {@code Status}
+     *                                                                 indicating the result of this action.
+     */
+    public Status addStaticHost(String networkAddress, String dataLayerAddress,
+                                NodeConnector nc, String vlan);
+
+    /**
+     * Allows the deletion of statically learned Host
+     *
+     * @param networkAddress
+     * @return                                         The status object as described in {@code Status}
+     *                                                                 indicating the result of this action.
+     */
+    public Status removeStaticHost(String networkAddress);
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java
new file mode 100644 (file)
index 0000000..88db081
--- /dev/null
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+/**
+ * This Interface defines the methods for client applications of
+ * Host Tracker to get notifications when a new host is learned or 
+ * existing host is removed from the network.
+ *
+ */
+public interface IfNewHostNotify {
+    /**
+     * Notifies the HostTracker Clients that a new Host has been learned
+     *
+     * @param host             Host Info encapsulated in HostNodeConnector class
+     */
+    public void notifyHTClient(HostNodeConnector host);
+
+    /**
+     * Notifies the HostTracker Clients that a Host which was learned in
+     * the past has been removed either due to switch/port down event or
+     * due to ARP Aging
+     *
+     * @param host             Host Info encapsulated in HostNodeConnector class
+     */
+    public void notifyHTClientHostRemoved(HostNodeConnector host);
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java
new file mode 100644 (file)
index 0000000..d18070d
--- /dev/null
@@ -0,0 +1,199 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.hostAware;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;
+
+@XmlRootElement(name="host")
+@XmlAccessorType(XmlAccessType.NONE)
+public class HostNodeConnector extends Host {
+    private static final long serialVersionUID = 1L;
+    @XmlElement
+    private NodeConnector nodeConnector;
+    @XmlElement
+    private short vlan;
+    @XmlElement
+    private boolean staticHost;
+    private transient short arpSendCountDown;
+
+    /**
+     * Private constructor used for JAXB mapping
+     */
+    private HostNodeConnector() {
+    }
+
+    public HostNodeConnector(InetAddress ip) throws ConstructionException {
+        this(ip, null);
+    }
+
+    public HostNodeConnector(InetAddress ip, NodeConnector nc)
+            throws ConstructionException {
+        this(new EthernetAddress(new byte[] { (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }), ip, nc,
+                (short) 0);
+    }
+
+    public HostNodeConnector(byte[] mac, InetAddress ip, NodeConnector nc,
+            short vlan) throws ConstructionException {
+        this(new EthernetAddress(mac.clone()), ip, nc, vlan);
+    }
+
+    public HostNodeConnector(EthernetAddress eaddr, InetAddress naddr,
+            NodeConnector nc, short vlan) throws ConstructionException {
+        super(eaddr, naddr);
+        this.nodeConnector = nc;
+        this.vlan = vlan;
+    }
+
+    /**
+     * @return the NodeConnector
+     */
+    public NodeConnector getnodeConnector() {
+        return this.nodeConnector;
+    }
+
+    /**
+     * @return the Node
+     */
+    public Node getnodeconnectorNode() {
+        return this.nodeConnector.getNode();
+    }
+
+    /**
+     * @return the NodeId
+     */
+    public Long getnodeconnectornodeId() {
+        return (Long) this.nodeConnector.getNode().getID();
+    }
+
+    /**
+     * @return the port
+     */
+    public Short getnodeconnectorportId() {
+        return (Short) this.nodeConnector.getID();
+    }
+
+    /**
+     * @return the DataLayerAddress
+     */
+    public byte[] getDataLayerAddressBytes() {
+        byte[] macaddr = null;
+        if (getDataLayerAddress() instanceof EthernetAddress) {
+            EthernetAddress e = (EthernetAddress) getDataLayerAddress();
+            macaddr = e.getValue();
+        }
+        return macaddr;
+    }
+
+    /**
+     * @return the vlan
+     */
+    public short getVlan() {
+        return this.vlan;
+    }
+
+    public boolean isStaticHost() {
+        return this.staticHost;
+    }
+
+    public HostNodeConnector setStaticHost(boolean statically_learned) {
+        this.staticHost = statically_learned;
+        return this;
+    }
+
+    public HostNodeConnector initArpSendCountDown() {
+        this.arpSendCountDown = 24;
+        return this;
+    }
+
+    public short getArpSendCountDown() {
+        return (this.arpSendCountDown);
+    }
+
+    public HostNodeConnector setArpSendCountDown(short cntdown) {
+        this.arpSendCountDown = cntdown;
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public boolean equalsByIP(InetAddress networkAddress) {
+        return (this.getNetworkAddress().equals(networkAddress));
+    }
+
+    public boolean isRewriteEnabled() {
+        byte[] emptyArray = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+        byte[] macaddr = null;
+        if (getDataLayerAddress() instanceof EthernetAddress) {
+            EthernetAddress e = (EthernetAddress) getDataLayerAddress();
+            macaddr = e.getValue();
+        }
+        if (macaddr == null)
+            return false;
+        return !Arrays.equals(emptyArray, macaddr);
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "HostNodeConnector[" + ReflectionToStringBuilder.toString(this)
+                + "]";
+    }
+
+    public boolean isV4Host() {
+        return (getNetworkAddress() instanceof Inet4Address);
+    }
+
+    public boolean isV6Host() {
+        return (getNetworkAddress() instanceof Inet6Address);
+    }
+
+    public String toJson() {
+        return "{\"host\":\"" + super.toString() + "\", " + "\"vlan\":\""
+                + String.valueOf(vlan) + "\",\"NodeConnector\":\""
+                + nodeConnector.toString() + "\"," + "\"static\":\""
+                + String.valueOf(isStaticHost()) + "\"}";
+    }
+
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java
new file mode 100644 (file)
index 0000000..f7151f7
--- /dev/null
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.hostAware;
+
+import java.net.InetAddress;
+
+/**
+ * This Interface  defines the methods to trigger the discovery of
+ * a Host and to probe if a learned Host is still in the network.
+ *
+ *
+ *
+ */
+public interface IHostFinder {
+    /**
+     * This method initiates the discovery of a host based on its IP address. This is triggered
+     * by query of an application to the HostTracker. The requested IP address
+     * doesn't exist in the local database at this point.
+     *
+     * @param networkAddress   IP Address encapsulated in InetAddress class
+     *
+     */
+    public void find(InetAddress networkAddress);
+
+    /**
+     * This method is called by HostTracker to see if a learned Host is still in the network.
+     * Used mostly for ARP Aging.
+     *
+     * @param host                     The Host that needs to be probed
+     */
+    public void probe(HostNodeConnector host);
+}
diff --git a/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java b/opendaylight/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java
new file mode 100644 (file)
index 0000000..25fe1f2
--- /dev/null
@@ -0,0 +1,133 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.hosttracker.HostTracker;
+import org.opendaylight.controller.hosttracker.IfHostListener;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.IfNewHostNotify;
+import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { HostTracker.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(HostTracker.class)) {
+            // export the service
+            c.setInterface(new String[] { ISwitchManagerAware.class.getName(),
+                    IInventoryListener.class.getName(),
+                    IfIptoHost.class.getName(), IfHostListener.class.getName(),
+                    ITopologyManagerAware.class.getName() }, null);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManager.class).setCallbacks("setSwitchManager",
+                    "unsetSwitchManager").setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IClusterContainerServices.class).setCallbacks(
+                    "setClusterContainerService",
+                    "unsetClusterContainerService").setRequired(true));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IHostFinder.class).setCallbacks("setArpHandler",
+                    "unsetArpHandler").setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ITopologyManager.class).setCallbacks("setTopologyManager",
+                    "unsetTopologyManager").setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IfNewHostNotify.class).setCallbacks("setnewHostNotify",
+                    "unsetnewHostNotify").setRequired(false));
+        }
+    }
+
+    /**
+     * Method which tells how many Global implementations are
+     * supported by the bundle. This way we can tune the number of
+     * components created. This components will be created ONLY at the
+     * time of bundle startup and will be destroyed only at time of
+     * bundle destruction, this is the major difference with the
+     * implementation retrieved via getImplementations where all of
+     * them are assumed to be in a container !
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * in Global version
+     */
+    protected Object[] getGlobalImplementations() {
+        return null;
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureGlobalInstance(Component c, Object imp) {
+        if (imp.equals(HostTracker.class)) {
+        }
+    }
+}
diff --git a/opendaylight/hosttracker/src/test/java/org/opendaylight/controller/hosttracker/HostTrackerTest.java b/opendaylight/hosttracker/src/test/java/org/opendaylight/controller/hosttracker/HostTrackerTest.java
new file mode 100644 (file)
index 0000000..482e064
--- /dev/null
@@ -0,0 +1,59 @@
+\r
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.hosttracker;\r
+\r
+\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+import java.util.concurrent.Future;\r
+\r
+import org.junit.Assert;\r
+import org.junit.Test;\r
+\r
+import junit.framework.TestCase;\r
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;\r
+\r
+public class HostTrackerTest extends TestCase {\r
+\r
+       @Test\r
+       public void testHostTrackerCallable() throws UnknownHostException {\r
+               \r
+               HostTracker hostTracker = null;\r
+               hostTracker = new HostTracker();\r
+               Assert.assertFalse(hostTracker== null);\r
+               \r
+               InetAddress hostIP = InetAddress.getByName("192.168.0.8");\r
+               \r
+               HostTrackerCallable htCallable = new HostTrackerCallable (hostTracker, hostIP);\r
+               Assert.assertTrue(htCallable.trackedHost.equals(hostIP));\r
+               Assert.assertTrue(htCallable.hostTracker.equals(hostTracker));\r
+\r
+               long count = htCallable.latch.getCount();\r
+               htCallable.wakeup();\r
+               Assert.assertTrue(htCallable.latch.getCount() == --count );\r
+       }               \r
+               \r
+       \r
+       \r
+       @Test\r
+       public void testHostTracker() throws UnknownHostException {\r
+               HostTracker hostTracker = null;\r
+               hostTracker = new HostTracker();\r
+               Assert.assertFalse(hostTracker== null);\r
+               \r
+               InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
+               InetAddress hostIP_2 = InetAddress.getByName("192.168.0.18");\r
+               Future<HostNodeConnector> dschost = hostTracker.discoverHost(hostIP_1);\r
+               dschost = hostTracker.discoverHost(hostIP_2);\r
+               hostTracker.nonClusterObjectCreate();\r
+       }\r
+       \r
+\r
+}\r
diff --git a/opendaylight/hosttracker/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java b/opendaylight/hosttracker/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java
new file mode 100644 (file)
index 0000000..ee38fc7
--- /dev/null
@@ -0,0 +1,90 @@
+\r
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.hosttracker.hostAware;\r
+\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+\r
+import org.junit.Assert;\r
+import org.junit.Test;\r
+\r
+import org.opendaylight.controller.sal.core.ConstructionException;\r
+import org.opendaylight.controller.sal.core.Node;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;\r
+\r
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;\r
+import org.opendaylight.controller.sal.core.NodeConnector;\r
+import org.opendaylight.controller.sal.utils.NodeCreator;\r
+\r
+\r
+public class HostNodeConnectorTest extends TestCase {\r
+\r
+       @Test\r
+       public void testHostNodeConnector() throws UnknownHostException {\r
+               HostNodeConnector hostnodeconnector_1, hostnodeconnector_2, hostnodeconnector_3;\r
+               InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
+               InetAddress hostIP_2 = InetAddress.getByName("2001:420:281:1004:e123:e688:d655:a1b0");\r
+               InetAddress hostIP_3 = InetAddress.getByName("192.168.0.28");\r
+               byte[] hostMAC_2 = new byte[]{(byte)0x11,(byte)0x22,(byte)0x33,(byte)0x22,(byte)0x22,(byte)0x22};\r
+               byte[] hostMAC_3 = new byte[]{(byte)0x11,(byte)0x22,(byte)0x33,(byte)0x33,(byte)0x33,(byte)0x33};\r
+               \r
+               Node node  = NodeCreator.createOFNode(1L);\r
+               NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector((short) 2, node);\r
+               NodeConnector nc2 = NodeConnectorCreator.createOFNodeConnector((short) 1, node);\r
+               \r
+               try {\r
+                       hostnodeconnector_1 = new HostNodeConnector(hostIP_1);\r
+                       Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
+                       Assert.assertTrue(hostnodeconnector_1.isV4Host());\r
+                       Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
+               } catch (ConstructionException e) {\r
+                       Assert.assertTrue(false);\r
+               }\r
+               \r
+               try {\r
+                       hostnodeconnector_2 = new HostNodeConnector(\r
+                               hostMAC_2, hostIP_2, nc1, (short)2);\r
+                       Assert.assertTrue(hostnodeconnector_2.isV6Host());\r
+                       Assert.assertTrue(hostnodeconnector_2.getnodeConnector().equals(nc1));\r
+                       Assert.assertTrue(hostnodeconnector_2.getnodeconnectorNode().equals(node));\r
+                       Assert.assertTrue(node.getID().equals(hostnodeconnector_2.getnodeconnectornodeId()));\r
+                       Assert.assertTrue(hostnodeconnector_2.getnodeconnectorportId().equals((short)2));\r
+               } catch (ConstructionException e) {\r
+                       Assert.assertTrue(false);\r
+               }\r
+               \r
+               try {\r
+                       hostnodeconnector_3 = new HostNodeConnector(\r
+                                       new EthernetAddress(hostMAC_3), hostIP_3, nc2, (short)3);\r
+                       byte[] hostMAC_3_rb = hostnodeconnector_3.getDataLayerAddressBytes();\r
+                       HostNodeConnector  hostnodeconnector_3rb = new HostNodeConnector(\r
+                                       new EthernetAddress(hostMAC_3_rb), hostIP_3, nc2, (short)3);\r
+                       Assert.assertTrue(hostnodeconnector_3.equals(hostnodeconnector_3rb));\r
+                       \r
+                       Assert.assertTrue(hostnodeconnector_3.getVlan() == (short)3);\r
+                       \r
+                       hostnodeconnector_3.setStaticHost(true);\r
+                       Assert.assertTrue(hostnodeconnector_3.isStaticHost());\r
+                       \r
+                       Assert.assertTrue(hostnodeconnector_3.isRewriteEnabled());\r
+                       \r
+                       hostnodeconnector_3.initArpSendCountDown().setArpSendCountDown((short) 10);\r
+                       Assert.assertTrue(hostnodeconnector_3.getArpSendCountDown() == (short)10);\r
+                       \r
+               } catch (ConstructionException e) {\r
+                       Assert.assertTrue(false);\r
+               }\r
+               \r
+       }\r
+\r
+}\r
diff --git a/opendaylight/northbound/commons/pom.xml b/opendaylight/northbound/commons/pom.xml
new file mode 100644 (file)
index 0000000..61e5304
--- /dev/null
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>commons.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+              org.opendaylight.controller.northbound.commons.exception,
+              org.opendaylight.controller.northbound.commons
+            </Export-Package>
+            <Import-Package>
+              javax.ws.rs,
+              javax.ws.rs.core,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.usermanager,
+              javax.servlet.http, 
+              org.slf4j, 
+              org.springframework.security.authentication, 
+              org.springframework.security.core, 
+              org.springframework.security.core.context, 
+              org.springframework.security.web.context               
+            </Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+      <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>usermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+               <groupId>org.springframework.security</groupId>
+               <artifactId>spring-security-core</artifactId>
+               <version>${spring-security.version}</version>
+               <scope>provided</scope>
+       </dependency>
+       <dependency>
+               <groupId>org.springframework.security</groupId>
+               <artifactId>spring-security-web</artifactId>
+               <version>${spring-security.version}</version>
+               <scope>provided</scope>
+       </dependency>        
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/AuthenticationProviderWrapper.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/AuthenticationProviderWrapper.java
new file mode 100644 (file)
index 0000000..821fb12
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+
+public class AuthenticationProviderWrapper implements AuthenticationProvider {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(AuthenticationProviderWrapper.class);
+
+    @Override
+    public Authentication authenticate(Authentication authentication)
+            throws AuthenticationException {
+        return ((AuthenticationProvider) getUserManagerRef())
+                .authenticate(authentication);
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return ((AuthenticationProvider) getUserManagerRef())
+                .supports(authentication);
+    }
+
+    private IUserManager getUserManagerRef() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager != null) {
+            return userManager;
+        } else {
+            logger.error("UserManager Ref is null. ");
+            throw new InternalServerErrorException("UserManager Ref is null. ");
+        }
+    }
+
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/RestMessages.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/RestMessages.java
new file mode 100644 (file)
index 0000000..ebe36a9
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons;
+
+public enum RestMessages {
+    SUCCESS("Success"), NOCONTAINER("Container does not exist"), NOFLOWSPEC(
+            "Flow Spec does not exist"), NOSUBNET("Subnet does not exist"), NOSTATICROUTE(
+            "Static Route does not exist"), NOHOST("Host does not exist"), NOFLOW(
+            "Flow does not exist"), NONODE("Node does not exist"), NOPOLICY(
+            "Policy does not exist"), NORESOURCE("Resource does not exist"), RESOURCECONFLICT(
+            "Operation failed due to Resource Conflict"), NODEFAULT(
+            "Container default is not a custom container"), DEFAULTDISABLED(
+            "Container(s) are configured. Container default is not operational"), NOTALLOWEDONDEFAULT(
+            "Container default is a static resource, no modification allowed on it"), UNKNOWNACTION(
+            "Unknown action"), INVALIDJSON("JSON message is invalid"), INVALIDADDRESS(
+            "invalid InetAddress"), AVAILABLESOON(
+            "Resource is not implemented yet"), INTERNALERROR("Internal Error"), SERVICEUNAVAILABLE(
+            "Service is not available. Could be down for maintanence"), INVALIDDATA(
+            "Data is invalid or conflicts with URI");
+
+    private String message;
+
+    private RestMessages(String msg) {
+        message = msg;
+    }
+
+    public String toString() {
+        return message;
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/WebSecurityContextRepository.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/WebSecurityContextRepository.java
new file mode 100644 (file)
index 0000000..9f1c4a5
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.web.context.HttpRequestResponseHolder;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+
+public class WebSecurityContextRepository implements SecurityContextRepository {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(WebSecurityContextRepository.class);
+
+    WebSecurityContextRepository() {
+    }
+
+    @Override
+    public SecurityContext loadContext(
+            HttpRequestResponseHolder requestResponseHolder) {
+
+        SecurityContextRepository contextRepo = (SecurityContextRepository) getUserManagerRef()
+                .getSecurityContextRepo();
+        return contextRepo.loadContext(requestResponseHolder);
+    }
+
+    @Override
+    public void saveContext(SecurityContext context,
+            HttpServletRequest request, HttpServletResponse response) {
+        SecurityContextRepository contextRepo = (SecurityContextRepository) getUserManagerRef()
+                .getSecurityContextRepo();
+        contextRepo.saveContext(context, request, response);
+    }
+
+    private IUserManager getUserManagerRef() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager != null) {
+            return userManager;
+        } else {
+            logger.error("UserManager Ref is null. ");
+            throw new InternalServerErrorException("UserManager Ref is null. ");
+        }
+    }
+
+    @Override
+    public boolean containsContext(HttpServletRequest request) {
+        SecurityContextRepository contextRepo = (SecurityContextRepository) getUserManagerRef()
+                .getSecurityContextRepo();
+        return contextRepo.containsContext(request);
+    }
+
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/InternalServerErrorException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/InternalServerErrorException.java
new file mode 100644 (file)
index 0000000..aa47a33
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 500 (Internal Server Error)
+ *
+ * The server encountered an unexpected condition which prevented
+ * it from fulfilling the request.
+ *
+ *
+ *
+ */
+public class InternalServerErrorException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructor for the INTERNAL_SERVER_ERROR custom handler
+     *
+     * @param string Error message to specify further the
+     * INTERNAL_SERVER_ERROR response
+     *
+     */
+    public InternalServerErrorException(String string) {
+        super(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
+                string).type(MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/MethodNotAllowed.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/MethodNotAllowed.java
new file mode 100644 (file)
index 0000000..2495bdc
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Implementation of StatusType for error 405 as in:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6
+ *
+ *
+ */
+public class MethodNotAllowed implements Response.StatusType {
+    @Override
+    public int getStatusCode() {
+        return 405;
+    }
+
+    @Override
+    public String getReasonPhrase() {
+        return "Method Not Allowed";
+    }
+
+    @Override
+    public Response.Status.Family getFamily() {
+        return Response.Status.Family.CLIENT_ERROR;
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/MethodNotAllowedException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/MethodNotAllowedException.java
new file mode 100644 (file)
index 0000000..8a7de2c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 405 (Method Not Allowed)
+ *
+ * The method specified in the Request-Line is not allowed for the
+ * resource identified by the Request-URI. The response MUST include
+ * an Allow header containing a list of valid methods for the
+ * requested resource.
+ *
+ *
+ *
+ */
+public class MethodNotAllowedException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public MethodNotAllowedException(String string) {
+        super(Response.status(new MethodNotAllowed()).entity(string).type(
+                MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/NotAcceptableException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/NotAcceptableException.java
new file mode 100644 (file)
index 0000000..3b926d6
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 406 (Not Acceptable)
+ *
+ * The resource identified by the request is only capable of
+ * generating response entities which have content characteristics not
+ * acceptable according to the accept headers sent in the request.
+ *
+ *
+ *
+ */
+public class NotAcceptableException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public NotAcceptableException(String string) {
+        super(Response.status(Response.Status.NOT_ACCEPTABLE).entity(string)
+                .type(MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceConflictException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceConflictException.java
new file mode 100644 (file)
index 0000000..2f1754b
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 409 (Conflict)
+ *
+ * The request could not be completed due to a conflict with the
+ * current state of the resource. This code is only allowed in
+ * situations where it is expected that the user might be able to
+ * resolve the conflict and resubmit the request. The response body
+ * SHOULD include enough information for the user to recognize the
+ * source of the conflict. Ideally, the response entity would include
+ * enough information for the user or user agent to fix the problem;
+ * however, that might not be possible and is not required.
+ *
+ *
+ *
+ */
+public class ResourceConflictException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public ResourceConflictException(String string) {
+        super(Response.status(Response.Status.CONFLICT).entity(string).type(
+                MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceForbiddenException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceForbiddenException.java
new file mode 100644 (file)
index 0000000..5997935
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 403 (Forbidden)
+ *
+ * The server understood the request, but is refusing to fulfill it.
+ * Authorization will not help and the request SHOULD NOT be repeated.
+ * If the request method was not HEAD and the server wishes to make public
+ * why the request has not been fulfilled, it SHOULD describe the reason
+ * for the refusal in the entity.
+ * If the server does not wish to make this information available to
+ * the client, the status code 404 (Not Found) can be used instead.
+ *
+ *
+ *
+ */
+public class ResourceForbiddenException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public ResourceForbiddenException(String string) {
+        super(Response.status(Response.Status.FORBIDDEN).entity(string).type(
+                MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceGoneException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceGoneException.java
new file mode 100644 (file)
index 0000000..f38e8b3
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 410 (Gone)
+ *
+ * The requested resource is no longer available at the server and no
+ * forwarding address is known. This condition is expected to be
+ * considered permanent. Clients with link editing capabilities SHOULD
+ * delete references to the Request-URI after user approval. If the
+ * server does not know, or has no facility to determine, whether or
+ * not the condition is permanent, the status code 404 (Not Found)
+ * SHOULD be used instead. This response is cacheable unless indicated
+ * otherwise.
+ *
+ *
+ *
+ */
+public class ResourceGoneException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public ResourceGoneException(String string) {
+        super(Response.status(Response.Status.GONE).entity(string).type(
+                MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceNotFoundException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ResourceNotFoundException.java
new file mode 100644 (file)
index 0000000..7bd09b7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 404 (Not Found)
+ *
+ * The server has not found anything matching the Request-URI.
+ * No indication is given of whether the condition is temporary or permanent.
+ * The 410 (Gone) status code SHOULD be used if the server knows,
+ * through some internally configurable mechanism, that an old resource
+ * is permanently unavailable and has no forwarding address.
+ * This status code is commonly used when the server does not wish to
+ * reveal exactly why the request has been refused, or when no other
+ * response is applicable.
+ *
+ *
+ *
+ */
+public class ResourceNotFoundException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public ResourceNotFoundException(String string) {
+        super(Response.status(Response.Status.NOT_FOUND).entity(string).type(
+                MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ServiceUnavailableException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/ServiceUnavailableException.java
new file mode 100644 (file)
index 0000000..74a8a94
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 503 (Service Unavailable Error)
+ *
+ * The server is currently unable to handle the request due to a temporary
+ * overloading or maintenance of the server.
+ * The implication is that this is a temporary condition which will be alleviated
+ * after some delay.
+ *
+ *
+ */
+public class ServiceUnavailableException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructor for the SERVICE_UNAVAILABLE custom handler
+     *
+     * @param string Error message to specify further the
+     * SERVICE_UNAVAILABLE response
+     *
+     */
+    public ServiceUnavailableException(String string) {
+        super(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity(
+                string).type(MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/UnauthorizedException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/UnauthorizedException.java
new file mode 100644 (file)
index 0000000..c7cb900
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 401 (Unauthorized)
+ *
+ * The request requires user authentication. The response MUST include
+ * a WWW-Authenticate header field (section 14.47) containing a
+ * challenge applicable to the requested resource.
+ *
+ *
+ *
+ */
+public class UnauthorizedException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public UnauthorizedException(String string) {
+        super(Response.status(Response.Status.UNAUTHORIZED).entity(string)
+                .type(MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/UnsupportedMediaTypeException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/UnsupportedMediaTypeException.java
new file mode 100644 (file)
index 0000000..c54d2cf
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northbound.commons.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Status Code 415 (Unsupported Media Type)
+ *
+ * The server is refusing to service the request because the entity of
+ * the request is in a format not supported by the requested resource
+ * for the requested method.
+ *
+ *
+ *
+ */
+public class UnsupportedMediaTypeException extends WebApplicationException {
+    private static final long serialVersionUID = 1L;
+
+    public UnsupportedMediaTypeException(String string) {
+        super(Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).entity(
+                string).type(MediaType.TEXT_PLAIN).build());
+    }
+}
diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/package-info.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/exception/package-info.java
new file mode 100644 (file)
index 0000000..db2a133
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * Provides the necessary class that represent exception in REST
+ * API.
+ *
+ */
+package org.opendaylight.controller.northbound.commons.exception;
+
diff --git a/opendaylight/northbound/flowprogrammer/enunciate.xml b/opendaylight/northbound/flowprogrammer/enunciate.xml
new file mode 100644 (file)
index 0000000..7bff102
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+  
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/flow"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="Flow Programmer REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/flowprogrammer/pom.xml b/opendaylight/northbound/flowprogrammer/pom.xml
new file mode 100644 (file)
index 0000000..14a77ae
--- /dev/null
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>flowprogrammer.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>    
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.controller.forwardingrulesmanager,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.containermanager,
+              org.opendaylight.controller.switchmanager,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              org.slf4j,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Export-Package>
+            </Export-Package>
+            <Web-ContextPath>/one/nb/v2/flow</Web-ContextPath>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>    
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>forwardingrulesmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+    <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowConfigs.java b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowConfigs.java
new file mode 100644 (file)
index 0000000..f6a757e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.flowprogrammer.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class FlowConfigs {
+       @XmlElement
+       List<FlowConfig> flowConfig;
+       //To satisfy JAXB
+       private FlowConfigs() {
+               
+       }
+       
+       public FlowConfigs(List<FlowConfig> flowConfig) {
+               this.flowConfig = flowConfig;
+       }
+
+       public List<FlowConfig> getFlowConfig() {
+               return flowConfig;
+       }
+
+       public void setFlowConfig(List<FlowConfig> flowConfig) {
+               this.flowConfig = flowConfig;
+       }
+}
diff --git a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java
new file mode 100644 (file)
index 0000000..e4ca83a
--- /dev/null
@@ -0,0 +1,416 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.flowprogrammer.northbound;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.xml.bind.JAXBElement;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.containermanager.IContainerManager;
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+import org.opendaylight.controller.northbound.commons.exception.NotAcceptableException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+/**
+ * Flow Configuration Northbound API
+ *
+ * <br><br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default. Administrator can enable it in tomcat-server.xml after adding 
+ * a proper keystore / SSL certificate from a trusted authority.<br>
+ * More info : http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
+ *
+ */
+@Path("/")
+public class FlowProgrammerNorthbound {
+
+    private IForwardingRulesManager getForwardingRulesManagerService(
+            String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new ServiceUnavailableException("Container "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        boolean found = false;
+        List<String> containerNames = containerManager.getContainerNames();
+        for (String cName : containerNames) {
+            if (cName.trim().equalsIgnoreCase(containerName.trim())) {
+                found = true;
+            }
+        }
+
+        if (found == false) {
+            throw new ResourceNotFoundException(containerName + " "
+                    + RestMessages.NOCONTAINER.toString());
+        }
+
+        IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper
+                .getInstance(IForwardingRulesManager.class, containerName, this);
+
+        if (frm == null) {
+            throw new ServiceUnavailableException("Flow Programmer "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        return frm;
+    }
+
+    private List<FlowConfig> getStaticFlowsInternal(String containerName,
+            Node node) {
+        IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
+
+        if (frm == null) {
+            throw new ServiceUnavailableException("Flow Programmer "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        List<FlowConfig> flows = new ArrayList<FlowConfig>();
+
+        if (node == null) {
+            for (FlowConfig flow : frm.getStaticFlows()) {
+                flows.add(flow);
+            }
+        } else {
+            ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
+                    ISwitchManager.class, containerName, this);
+
+            if (sm == null) {
+                throw new ServiceUnavailableException("Switch Manager "
+                        + RestMessages.SERVICEUNAVAILABLE.toString());
+            }
+
+            if (!sm.getNodes().contains(node)) {
+                throw new ResourceNotFoundException(node.toString() + " : "
+                        + RestMessages.NONODE.toString());
+            }
+
+            for (FlowConfig flow : frm.getStaticFlows(node)) {
+                flows.add(flow);
+            }
+        }
+        return flows;
+    }
+
+    /**
+     * Returns a list of Flows configured on the given container
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @return List of configured flows configured on a given container
+     */
+    @Path("/{containerName}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(FlowConfigs.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public FlowConfigs getStaticFlows(
+            @PathParam("containerName") String containerName) {
+        List<FlowConfig> flowConfigs = getStaticFlowsInternal(containerName, null);
+        return new FlowConfigs(flowConfigs);
+    }
+
+    /**
+     * Returns a list of Flows configured on a Node in a given container
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default".
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier
+     * @return List of configured flows configured on a Node in a container
+     */
+    @Path("/{containerName}/{nodeType}/{nodeId}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(FlowConfigs.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName or nodeId is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public FlowConfigs getStaticFlows(
+            @PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId) {
+        Node node = Node.fromString(nodeType, nodeId);
+        if (node == null) {
+            throw new ResourceNotFoundException(nodeId + " : "
+                    + RestMessages.NONODE.toString());
+        }
+        List<FlowConfig> flows = getStaticFlowsInternal(containerName, node);
+        return new FlowConfigs(flows);
+    }
+
+    /**
+     * Returns the flow configuration matching a human-readable name and nodeId on a
+     * given Container.
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default".
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier
+     * @param name Human-readable name for the configured flow.
+     * @return Flow configuration matching the name and nodeId on a Container
+     */
+    @Path("/{containerName}/{nodeType}/{nodeId}/{name}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(FlowConfig.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName or NodeId or Configuration name is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public FlowConfig getStaticFlow(
+            @PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId,
+            @PathParam("name") String name) {
+        IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
+
+        if (frm == null) {
+            throw new ServiceUnavailableException("Flow Programmer "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName, nodeType, nodeId);
+
+        FlowConfig staticFlow = frm.getStaticFlow(name, node);
+        if (staticFlow == null) {
+            throw new ResourceNotFoundException(RestMessages.NOFLOW.toString());
+        }
+
+        return new FlowConfig(staticFlow);
+    }
+
+    /**
+     * Add a flow configuration
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default".
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier
+     * @param name Name of the Static Flow configuration
+     * @param FlowConfig Flow Configuration in JSON or XML format
+     * @return Response as dictated by the HTTP Response Status code
+     */
+
+    @Path("/{containerName}/{nodeType}/{nodeId}/{name}")
+    @POST
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 201, condition = "Flow Config processed successfully"),
+            @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
+            @ResponseCode(code = 409, condition = "Failed to create Static Flow entry due to Conflicting Name"),
+            @ResponseCode(code = 500, condition = "Failed to create Static Flow entry. Failure Reason included in HTTP Error response"),
+            @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
+    public Response addFlow(
+            @PathParam(value = "containerName") String containerName,
+            @PathParam(value = "name") String name,
+            @PathParam("nodeType") String nodeType,
+            @PathParam(value = "nodeId") String nodeId,
+            @TypeHint(FlowConfig.class) JAXBElement<FlowConfig> flowConfig) {
+
+        handleDefaultDisabled(containerName);
+
+        IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
+
+        if (frm == null) {
+            throw new ServiceUnavailableException("Flow Programmer "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName, nodeType, nodeId);
+
+        FlowConfig staticFlow = frm.getStaticFlow(name, node);
+        if (staticFlow != null) {
+            throw new ResourceConflictException(name + " already exists."
+                    + RestMessages.RESOURCECONFLICT.toString());
+        }
+
+        Status status = frm.addStaticFlow(flowConfig.getValue(), false);
+        if (status.isSuccess()) {
+            return Response.status(Response.Status.CREATED).build();
+        }
+        throw new InternalServerErrorException(status.getDescription());
+    }
+
+    /**
+     * Delete a Flow configuration
+     *
+     * DELETE /flows/{containerName}/{nodeType}/{nodeId}/{name}
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default".
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier
+     * @param name Name of the Static Flow configuration
+     * @return Response as dictated by the HTTP Response code
+     */
+
+    @Path("/{containerName}/{nodeType}/{nodeId}/{name}")
+    @DELETE
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
+            @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
+            @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
+            @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
+    public Response deleteFlow(
+            @PathParam(value = "containerName") String containerName,
+            @PathParam(value = "name") String name,
+            @PathParam("nodeType") String nodeType,
+            @PathParam(value = "nodeId") String nodeId) {
+
+        handleDefaultDisabled(containerName);
+
+        IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
+
+        if (frm == null) {
+            throw new ServiceUnavailableException("Flow Programmer "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName, nodeType, nodeId);
+
+        FlowConfig staticFlow = frm.getStaticFlow(name, node);
+        if (staticFlow == null) {
+            throw new ResourceNotFoundException(name + " : "
+                    + RestMessages.NOFLOW.toString());
+        }
+
+        Status status = frm.removeStaticFlow(name, node);
+        if (status.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new InternalServerErrorException(status.getDescription());
+    }
+
+    /**
+     * Toggle a Flow configuration
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default".
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier
+     * @param name Name of the Static Flow configuration
+     * @return Response as dictated by the HTTP Response code
+     */
+
+    @Path("/{containerName}/{nodeType}/{nodeId}/{name}")
+    @PUT
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
+            @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
+            @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
+            @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
+    public Response toggleFlow(
+            @PathParam(value = "containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam(value = "nodeId") String nodeId,
+            @PathParam(value = "name") String name) {
+
+        handleDefaultDisabled(containerName);
+
+        IForwardingRulesManager frm = getForwardingRulesManagerService(containerName);
+
+        if (frm == null) {
+            throw new ServiceUnavailableException("Flow Programmer "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName, nodeType, nodeId);
+
+        FlowConfig staticFlow = frm.getStaticFlow(name, node);
+        if (staticFlow == null) {
+            throw new ResourceNotFoundException(name + " : "
+                    + RestMessages.NOFLOW.toString());
+        }
+
+        Status status = frm.toggleStaticFlowStatus(new FlowConfig("", name,
+                node, "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+                "", "", "", null));
+        if (status.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new InternalServerErrorException(status.getDescription());
+    }
+
+    private Node handleNodeAvailability(String containerName, String nodeType,
+                                        String nodeId) {
+
+        Node node = Node.fromString(nodeType, nodeId);
+        if (node == null) {
+            throw new ResourceNotFoundException(nodeId + " : "
+                    + RestMessages.NONODE.toString());
+        }
+
+        ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+
+        if (sm == null) {
+            throw new ServiceUnavailableException("Switch Manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        if (!sm.getNodes().contains(node)) {
+            throw new ResourceNotFoundException(node.toString() + " : "
+                    + RestMessages.NONODE.toString());
+        }
+        return node;
+    }
+
+    private void handleDefaultDisabled(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new InternalServerErrorException(RestMessages.INTERNALERROR
+                    .toString());
+        }
+        if (containerName.equals(GlobalConstants.DEFAULT.toString())
+                && containerManager.hasNonDefaultContainer()) {
+            throw new NotAcceptableException(RestMessages.DEFAULTDISABLED
+                    .toString());
+        }
+    }
+
+}
diff --git a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthboundRSApplication.java b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..3c6a5f8
--- /dev/null
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.flowprogrammer.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * Instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class FlowProgrammerNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(FlowProgrammerNorthbound.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/flowprogrammer/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..bce59ee
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..df8cba1
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  
+       <servlet>
+         <servlet-name>JAXRSFlowProgrammer</servlet-name>
+         <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+         <init-param>
+           <param-name>javax.ws.rs.Application</param-name>
+           <param-value>org.opendaylight.controller.flowprogrammer.northbound.FlowProgrammerNorthboundRSApplication</param-value>
+         </init-param>
+         <load-on-startup>1</load-on-startup>
+       </servlet>
+       
+       <servlet-mapping>
+         <servlet-name>JAXRSFlowProgrammer</servlet-name>
+         <url-pattern>/*</url-pattern>
+       </servlet-mapping>
+       
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
\ No newline at end of file
diff --git a/opendaylight/northbound/hosttracker/enunciate.xml b/opendaylight/northbound/hosttracker/enunciate.xml
new file mode 100644 (file)
index 0000000..43a01e5
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+  
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/host"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="Host Tracker REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/hosttracker/pom.xml b/opendaylight/northbound/hosttracker/pom.xml
new file mode 100644 (file)
index 0000000..297f699
--- /dev/null
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>hosttracker.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>    
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.controller.hosttracker,
+              org.opendaylight.controller.hosttracker.hostAware,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.containermanager,
+              org.opendaylight.controller.switchmanager,
+              org.apache.commons.logging,
+              com.sun.jersey.spi.container.servlet,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              org.slf4j,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Web-ContextPath>/one/nb/v2/host</Web-ContextPath>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>hosttracker</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+    <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java b/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java
new file mode 100644 (file)
index 0000000..0ed8306
--- /dev/null
@@ -0,0 +1,342 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.northbound;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.containermanager.IContainerManager;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
+import org.opendaylight.controller.northbound.commons.exception.UnsupportedMediaTypeException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+/**
+ * Host Tracker Northbound REST APIs.<br>
+ * This class provides REST APIs to track host location in a network. Host Location is represented by Host node connector 
+ * which is essentially a logical entity that represents a Switch/Port. A host is represented by it's IP-address 
+ * and mac-address.
+ *
+ * <br><br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default. Administrator can enable it in tomcat-server.xml after adding
+ * a proper keystore / SSL certificate from a trusted authority.<br>
+ * More info : http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
+ *
+ */
+
+@Path("/")
+public class HostTrackerNorthbound {
+
+    private IfIptoHost getIfIpToHostService(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new ServiceUnavailableException("Container "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        boolean found = false;
+        List<String> containerNames = containerManager.getContainerNames();
+        for (String cName : containerNames) {
+            if (cName.trim().equalsIgnoreCase(containerName.trim())) {
+                found = true;
+            }
+        }
+
+        if (found == false) {
+            throw new ResourceNotFoundException(containerName + " "
+                    + RestMessages.NOCONTAINER.toString());
+        }
+
+        IfIptoHost hostTracker = (IfIptoHost) ServiceHelper.getInstance(
+                IfIptoHost.class, containerName, this);
+
+        if (hostTracker == null) {
+            throw new ServiceUnavailableException("Host Tracker "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        return hostTracker;
+    }
+
+    /**
+     * Returns a list of all Hosts : both configured via PUT API and dynamically learnt on the network.
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @return List of Active Hosts.
+     */
+    @Path("/{containerName}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(Hosts.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public Hosts getActiveHosts(
+            @PathParam("containerName") String containerName) {
+        IfIptoHost hostTracker = getIfIpToHostService(containerName);
+        if (hostTracker == null) {
+            throw new ServiceUnavailableException("Host Tracker "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        return new Hosts(hostTracker.getAllHosts());
+    }
+
+    /**
+     * Returns a list of Hosts that are statically configured and are connected to a NodeConnector that is down.
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @return List of inactive Hosts.
+     */
+    @Path("/{containerName}/inactive")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(Hosts.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public Hosts getInactiveHosts(
+            @PathParam("containerName") String containerName) {
+        IfIptoHost hostTracker = getIfIpToHostService(containerName);
+        if (hostTracker == null) {
+            throw new ServiceUnavailableException("Host Tracker "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        return new Hosts(hostTracker.getInactiveStaticHosts());
+    }
+
+    /**
+     * Returns a host that matches the IP Address value passed as parameter.
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @param networkAddress IP Address being looked up
+     * @return host that matches the IP Address
+     */
+    @Path("/{containerName}/{networkAddress}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(HostNodeConnector.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 415, condition = "Invalid IP Address passed in networkAddress parameter"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public HostNodeConnector getHostDetails(
+            @PathParam("containerName") String containerName,
+            @PathParam("networkAddress") String networkAddress) {
+        IfIptoHost hostTracker = getIfIpToHostService(containerName);
+        if (hostTracker == null) {
+            throw new ServiceUnavailableException("Host Tracker "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        InetAddress ip;
+        try {
+            ip = InetAddress.getByName(networkAddress);
+        } catch (UnknownHostException e) {
+            throw new UnsupportedMediaTypeException(networkAddress + " "
+                    + RestMessages.INVALIDADDRESS.toString());
+        }
+        for (HostNodeConnector host : hostTracker.getAllHosts()) {
+            if (host.getNetworkAddress().equals(ip)) {
+                return host;
+            }
+        }
+        throw new ResourceNotFoundException(RestMessages.NOHOST.toString());
+    }
+
+    /**
+     * Add a Static Host configuration
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @param networkAddress Host IP Address
+     * @param dataLayerAddress Host L2 data-layer address.
+     * @param nodeType Node Type as specifid by Node class
+     * @param nodeId Node Identifier as specifid by Node class
+     * @param nodeConnectorType Port Type as specified by NodeConnector class
+     * @param nodeConnectorId Port Identifier as specified by NodeConnector class
+     * @param vlan Vlan number
+     * @return Response as dictated by the HTTP Response Status code
+     */
+
+    @Path("/{containerName}/{networkAddress}")
+    @POST
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 201, condition = "Flow Config processed successfully"),
+            @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
+            @ResponseCode(code = 415, condition = "Invalid IP Address passed in networkAddress parameter"),
+            @ResponseCode(code = 500, condition = "Failed to create Static Host entry. Failure Reason included in HTTP Error response"),
+            @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
+    public Response addHost(@PathParam("containerName") String containerName,
+            @PathParam("networkAddress") String networkAddress,
+            @QueryParam("dataLayerAddress") String dataLayerAddress,
+            @QueryParam("nodeType") String nodeType,
+            @QueryParam("nodeId") String nodeId,
+            @QueryParam("nodeConnectorType") String nodeConnectorType,
+            @QueryParam("nodeConnectorId") String nodeConnectorId,
+            @DefaultValue("0") @QueryParam("vlan") String vlan) {
+
+        handleDefaultDisabled(containerName);
+
+        IfIptoHost hostTracker = getIfIpToHostService(containerName);
+        if (hostTracker == null) {
+            throw new ServiceUnavailableException("Host Tracker "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName, nodeType, nodeId);
+        if (node == null) {
+            throw new InternalServerErrorException(RestMessages.NONODE.
+                                                   toString());
+        }
+
+        try {
+            InetAddress.getByName(networkAddress);
+        } catch (UnknownHostException e) {
+            throw new UnsupportedMediaTypeException(networkAddress + " "
+                    + RestMessages.INVALIDADDRESS.toString());
+        }
+        NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorType, nodeConnectorId,
+                                                          node);
+        if (nc == null) {
+            throw new ResourceNotFoundException(nodeConnectorType+"|"+nodeConnectorId + " : "
+                    + RestMessages.NONODE.toString());
+        }
+        Status status = hostTracker.addStaticHost(networkAddress,
+                                               dataLayerAddress,
+                                               nc, vlan);
+        if (status.isSuccess()) {
+            return Response.status(Response.Status.CREATED).build();
+        } else if (status.getCode().equals(StatusCode.BADREQUEST)) {
+            throw new UnsupportedMediaTypeException(status.getDescription());
+        }
+        throw new InternalServerErrorException(status.getDescription());
+    }
+
+    /**
+     * Delete a Static Host configuration
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @param networkAddress   IP Address
+     * @return Response as dictated by the HTTP Response code
+     */
+
+    @Path("/{containerName}/{networkAddress}")
+    @DELETE
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Flow Config deleted successfully"),
+            @ResponseCode(code = 404, condition = "The Container Name or Node-id or Flow Name passed is not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
+            @ResponseCode(code = 415, condition = "Invalid IP Address passed in networkAddress parameter"),
+            @ResponseCode(code = 500, condition = "Failed to delete Flow config. Failure Reason included in HTTP Error response"),
+            @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
+    public Response deleteFlow(
+            @PathParam(value = "containerName") String containerName,
+            @PathParam(value = "networkAddress") String networkAddress) {
+
+        handleDefaultDisabled(containerName);
+        IfIptoHost hostTracker = getIfIpToHostService(containerName);
+        if (hostTracker == null) {
+            throw new ServiceUnavailableException("Host Tracker "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        try {
+            InetAddress.getByName(networkAddress);
+        } catch (UnknownHostException e) {
+            throw new UnsupportedMediaTypeException(networkAddress + " "
+                    + RestMessages.INVALIDADDRESS.toString());
+        }
+
+        Status status = hostTracker.removeStaticHost(networkAddress);
+        if (status.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new InternalServerErrorException(status.getDescription());
+
+    }
+
+    private void handleDefaultDisabled(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new InternalServerErrorException(RestMessages.INTERNALERROR
+                    .toString());
+        }
+        if (containerName.equals(GlobalConstants.DEFAULT.toString())
+                && containerManager.hasNonDefaultContainer()) {
+            throw new ResourceConflictException(RestMessages.DEFAULTDISABLED
+                    .toString());
+        }
+    }
+
+    private Node handleNodeAvailability(String containerName, String nodeType,
+                                        String nodeId) {
+
+        Node node = Node.fromString(nodeType, nodeId);
+        if (node == null) {
+            throw new ResourceNotFoundException(nodeId + " : "
+                    + RestMessages.NONODE.toString());
+        }
+
+        ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+
+        if (sm == null) {
+            throw new ServiceUnavailableException("Switch Manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        if (!sm.getNodes().contains(node)) {
+            throw new ResourceNotFoundException(node.toString() + " : "
+                    + RestMessages.NONODE.toString());
+        }
+        return node;
+    }
+}
diff --git a/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthboundRSApplication.java b/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..89bee5b
--- /dev/null
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * This class is an instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class HostTrackerNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(HostTrackerNorthbound.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/Hosts.java b/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/Hosts.java
new file mode 100644 (file)
index 0000000..463b493
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.northbound;
+
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class Hosts {
+       @XmlElement (name="host")
+       Set<HostNodeConnector> hostNodeConnector;
+       
+       public Hosts() {
+       }
+       public Hosts (Set<HostNodeConnector> hostNodeConnector) {
+               this.hostNodeConnector = hostNodeConnector;
+       }
+       public Set<HostNodeConnector> getHostNodeConnector() {
+               return hostNodeConnector;
+       }
+       public void setHostNodeConnector(Set<HostNodeConnector> hostNodeConnector) {
+               this.hostNodeConnector = hostNodeConnector;
+       }
+}
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/hosttracker/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..bce59ee
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..bfa9fc7
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  <servlet>
+    <servlet-name>JAXRSHostTracker</servlet-name>
+    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.hosttracker.northbound.HostTrackerNorthboundRSApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JAXRSHostTracker</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
\ No newline at end of file
diff --git a/opendaylight/northbound/integrationtest/pom.xml b/opendaylight/northbound/integrationtest/pom.xml
new file mode 100644 (file)
index 0000000..102c5ad
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.onecontroller</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/onecontroller</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>northbound.integrationtest</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal.implementation</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/integrationtest/src/test/resources/exam.properties b/opendaylight/northbound/integrationtest/src/test/resources/exam.properties
new file mode 100644 (file)
index 0000000..d5f9ae1
--- /dev/null
@@ -0,0 +1,4 @@
+#pax.exam.system = default
+pax.exam.logging = none
+pax.exam.service.timeout = 5000
+
diff --git a/opendaylight/northbound/integrationtest/src/test/resources/logback.xml b/opendaylight/northbound/integrationtest/src/test/resources/logback.xml
new file mode 100644 (file)
index 0000000..2d63ce5
--- /dev/null
@@ -0,0 +1,13 @@
+<configuration scan="true">
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+      </pattern>
+    </encoder>
+  </appender>
+
+  <root level="error">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>
diff --git a/opendaylight/northbound/staticrouting/enunciate.xml b/opendaylight/northbound/staticrouting/enunciate.xml
new file mode 100644 (file)
index 0000000..b79b3e8
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+  
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/staticroute"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="Static Routing REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/staticrouting/pom.xml b/opendaylight/northbound/staticrouting/pom.xml
new file mode 100644 (file)
index 0000000..7382d9d
--- /dev/null
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>forwarding.staticrouting.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+            </Export-Package>
+            <Import-Package>
+              org.opendaylight.controller.forwarding.staticrouting,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.containermanager,
+              com.sun.jersey.spi.container.servlet,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              org.slf4j,              
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Export-Package>
+            </Export-Package>
+            <Web-ContextPath>/one/nb/v2/staticroute</Web-ContextPath>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>forwarding.staticrouting</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+    <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>   
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoute.java b/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoute.java
new file mode 100644 (file)
index 0000000..ffa80f6
--- /dev/null
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.northbound;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+/**
+ * This class contains static route data  that is returned as a response to the NorthBound GET request.
+ *
+ *
+ *
+ */
+public class StaticRoute {
+    @XmlElement
+    private String name;
+    @XmlElement
+    private String prefix; // A.B.C.D/MM  Where A.B.C.D is the Default Gateway IP (L3) or ARP Querier IP (L2)
+    @XmlElement
+    private String nextHop; // NextHop IP-Address (or) datapath ID/port list: xx:xx:xx:xx:xx:xx:xx:xx/a,b,c-m,r-t,y
+
+    public StaticRoute() {
+    }
+
+    public StaticRoute(String name, String prefix, String nextHop) {
+        super();
+        this.name = name;
+        this.prefix = prefix;
+        this.nextHop = nextHop;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+
+    public String getNextHop() {
+        return nextHop;
+    }
+
+    public void setNextHop(String nextHop) {
+        this.nextHop = nextHop;
+    }
+}
diff --git a/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutes.java b/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutes.java
new file mode 100644 (file)
index 0000000..9511b8c
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class StaticRoutes {
+       @XmlElement
+       List<StaticRoute> staticRoute;
+       //To satisfy JAXB
+       private StaticRoutes() {
+               
+       }
+       
+       public StaticRoutes(List<StaticRoute> staticRoute) {
+               this.staticRoute = staticRoute;
+       }
+
+       public List<StaticRoute> getFlowConfig() {
+               return staticRoute;
+       }
+
+       public void setFlowConfig(List<StaticRoute> staticRoute) {
+               this.staticRoute = staticRoute;
+       }
+}
diff --git a/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java b/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java
new file mode 100644 (file)
index 0000000..f04c902
--- /dev/null
@@ -0,0 +1,217 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.northbound;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.xml.bind.JAXBElement;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.containermanager.IContainerManager;
+import org.opendaylight.controller.forwarding.staticrouting.IForwardingStaticRouting;
+import org.opendaylight.controller.forwarding.staticrouting.StaticRouteConfig;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+import org.opendaylight.controller.northbound.commons.exception.NotAcceptableException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * Static Routing Northbound APIs
+ *
+ * <br><br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default. Administrator can enable it in tomcat-server.xml after adding 
+ * a proper keystore / SSL certificate from a trusted authority.<br>
+ * More info : http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
+ */
+@Path("/")
+public class StaticRoutingNorthbound {
+
+    private List<StaticRoute> getStaticRoutesInternal(String containerName) {
+
+        IForwardingStaticRouting staticRouting = (IForwardingStaticRouting) ServiceHelper
+                .getInstance(IForwardingStaticRouting.class, containerName,
+                        this);
+
+        if (staticRouting == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+
+        List<StaticRoute> routes = new ArrayList<StaticRoute>();
+
+        for (StaticRouteConfig conf : staticRouting.getStaticRouteConfigs()
+                .values()) {
+            StaticRoute route = new StaticRoute(conf.getName(), conf
+                    .getStaticRoute(), conf.getNextHop());
+            routes.add(route);
+        }
+        return routes;
+    }
+
+    /**
+     * Returns a list of static routes present on the given container
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @return List of configured static routes on the given container
+     */
+    @Path("/{containerName}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(StaticRoutes.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName passed was not found") })
+    public StaticRoutes getStaticRoutes(
+            @PathParam("containerName") String containerName) {
+        return new StaticRoutes(getStaticRoutesInternal(containerName));
+    }
+
+    /**
+     * Returns the static route for the provided configuration name on a given container
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @param name Name of the Static Route configuration
+     * @return Static route configured with the supplied Name.
+     */
+    @Path("/{containerName}/{name}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(StaticRoute.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The Container Name or Static Route Configuration name passed was not found") })
+    public StaticRoute getStaticRoute(
+            @PathParam("containerName") String containerName,
+            @PathParam("name") String name) {
+        List<StaticRoute> routes = this.getStaticRoutesInternal(containerName);
+        for (StaticRoute route : routes) {
+            if (route.getName().equalsIgnoreCase(name)) {
+                return route;
+            }
+        }
+
+        throw new ResourceNotFoundException(RestMessages.NOSTATICROUTE
+                .toString());
+    }
+
+    /**
+     *
+     * Add a new Static Route
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @param name Name of the Static Route configuration
+     * @return Response as dictated by the HTTP Response code
+     */
+    @Path("/{containerName}/{name}")
+    @POST
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 201, condition = "Created Static Route successfully"),
+            @ResponseCode(code = 404, condition = "The Container Name passed is not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
+            @ResponseCode(code = 409, condition = "Failed to create Static Route entry due to Conflicting Name or Prefix."), })
+    public Response addStaticRoute(
+            @PathParam(value = "containerName") String containerName,
+            @PathParam(value = "name") String name,
+            @TypeHint(StaticRoute.class) JAXBElement<StaticRoute> staticRouteData) {
+
+        handleDefaultDisabled(containerName);
+
+        IForwardingStaticRouting staticRouting = (IForwardingStaticRouting) ServiceHelper
+                .getInstance(IForwardingStaticRouting.class, containerName,
+                        this);
+
+        if (staticRouting == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+
+        StaticRoute sRoute = staticRouteData.getValue();
+        StaticRouteConfig cfgObject = new StaticRouteConfig(sRoute.getName(),
+                sRoute.getPrefix(), sRoute.getNextHop());
+        Status response = staticRouting.addStaticRoute(cfgObject);
+        if (response.isSuccess()) {
+            return Response.status(Response.Status.CREATED).build();
+        }
+        throw new ResourceConflictException(response.getDescription());
+    }
+
+    /**
+     *
+     * Delete a Static Route
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default".
+     * @param name Name of the Static Route configuration to be removed
+     *
+     * @return Response as dictated by the HTTP Response code
+     */
+
+    @Path("/{containerName}/{name}")
+    @DELETE
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "Container Name or Configuration Name not found"),
+            @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active") })
+    public Response removeStaticRoute(
+            @PathParam(value = "containerName") String containerName,
+            @PathParam(value = "name") String name) {
+
+        handleDefaultDisabled(containerName);
+
+        IForwardingStaticRouting staticRouting = (IForwardingStaticRouting) ServiceHelper
+                .getInstance(IForwardingStaticRouting.class, containerName,
+                        this);
+
+        if (staticRouting == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+
+        Status status = staticRouting.removeStaticRoute(name);
+        if (status.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new ResourceNotFoundException(status.getDescription());
+    }
+
+    private void handleDefaultDisabled(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new InternalServerErrorException(RestMessages.INTERNALERROR
+                    .toString());
+        }
+        if (containerName.equals(GlobalConstants.DEFAULT.toString())
+                && containerManager.hasNonDefaultContainer()) {
+            throw new NotAcceptableException(RestMessages.DEFAULTDISABLED
+                    .toString());
+        }
+    }
+}
diff --git a/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthboundRSApplication.java b/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..a39794d
--- /dev/null
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.forwarding.staticrouting.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * Instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class StaticRoutingNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(StaticRoutingNorthbound.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/staticrouting/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..bce59ee
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..da8937e
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  <servlet>
+    <servlet-name>JAXRSStaticRouting</servlet-name>
+    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.forwarding.staticrouting.northbound.StaticRoutingNorthboundRSApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JAXRSStaticRouting</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
\ No newline at end of file
diff --git a/opendaylight/northbound/statistics/enunciate.xml b/opendaylight/northbound/statistics/enunciate.xml
new file mode 100644 (file)
index 0000000..422ebda
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+  
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/statistics"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="Statistics REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/statistics/pom.xml b/opendaylight/northbound/statistics/pom.xml
new file mode 100644 (file)
index 0000000..a40c201
--- /dev/null
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+  
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>statistics.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>        
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+            </Export-Package>
+            <Import-Package>
+                         org.opendaylight.controller.forwardingrulesmanager,
+                         org.opendaylight.controller.sal.action,
+                         org.opendaylight.controller.sal.core,
+                         org.opendaylight.controller.sal.flowprogrammer,
+                         org.opendaylight.controller.sal.match,
+                         org.opendaylight.controller.sal.packet,
+                         org.opendaylight.controller.sal.reader,
+                         org.opendaylight.controller.sal.utils,
+                         org.opendaylight.controller.containermanager,
+                         org.opendaylight.controller.statisticsmanager,
+                         org.opendaylight.controller.switchmanager,
+                         org.apache.commons.logging,
+              com.sun.jersey.spi.container.servlet,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              org.slf4j,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Export-Package>
+            </Export-Package>
+            <Web-ContextPath>/one/nb/v2/statistics</Web-ContextPath>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>statisticsmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+       
+       <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>
+    
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+        
+       <dependency>
+               <groupId>org.springframework</groupId>
+               <artifactId>spring-web</artifactId>
+               <version>${spring.version}</version>
+               <scope>provided</scope>
+       </dependency>
+
+       <dependency>
+               <groupId>org.springframework.security</groupId>
+               <artifactId>spring-security-config</artifactId>
+               <version>${spring-security.version}</version>
+               <scope>provided</scope>
+       </dependency>
+       
+       <dependency>
+               <groupId>org.springframework.security</groupId>
+               <artifactId>spring-security-web</artifactId>
+               <version>${spring-security.version}</version>
+               <scope>provided</scope>
+       </dependency>
+        
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+       
+       
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/AllFlowStatistics.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/AllFlowStatistics.java
new file mode 100644 (file)
index 0000000..0e96c54
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statistics.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class AllFlowStatistics {
+       @XmlElement
+       List<FlowStatistics> flowStatistics;
+       //To satisfy JAXB
+       private AllFlowStatistics() {
+       }
+       
+       public AllFlowStatistics(List<FlowStatistics> flowStatistics) {
+               this.flowStatistics = flowStatistics;
+       }
+
+       public List<FlowStatistics> getFlowStatistics() {
+               return flowStatistics;
+       }
+
+       public void setFlowStatistics(List<FlowStatistics> flowStatistics) {
+               this.flowStatistics = flowStatistics;
+       }
+
+}
diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/AllPortStatistics.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/AllPortStatistics.java
new file mode 100644 (file)
index 0000000..a1bf190
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statistics.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class AllPortStatistics {
+       @XmlElement
+       List<PortStatistics> portStatistics;
+       //To satisfy JAXB
+       private AllPortStatistics() {
+       }
+       
+       public AllPortStatistics(List<PortStatistics> portStatistics) {
+               this.portStatistics = portStatistics;
+       }
+
+       public List<PortStatistics> getPortStatistics() {
+               return portStatistics;
+       }
+
+       public void setPortStatistics(List<PortStatistics> portStatistics) {
+               this.portStatistics = portStatistics;
+       }
+
+}
diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/FlowStatistics.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/FlowStatistics.java
new file mode 100644 (file)
index 0000000..146e95a
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statistics.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class FlowStatistics {
+    @XmlElement
+    private Node node;
+    @XmlElement(name="flowStat")
+    private List<FlowOnNode> flowStat;
+
+    // To satisfy JAXB
+    @SuppressWarnings("unused")
+       private FlowStatistics() {
+    }
+
+    public FlowStatistics(Node node, List<FlowOnNode> flowStat) {
+        super();
+        this.node = node;
+        this.flowStat = flowStat;
+    }
+
+    public Node getNode() {
+        return node;
+    }
+
+    public void setNode(Node node) {
+        this.node = node;
+    }
+
+    public List<FlowOnNode> getFlowStats() {
+        return flowStat;
+    }
+
+    public void setFlowStats(List<FlowOnNode> flowStats) {
+        this.flowStat = flowStats;
+    }
+}
diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/PortStatistics.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/PortStatistics.java
new file mode 100644 (file)
index 0000000..068baa1
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statistics.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class PortStatistics {
+    @XmlElement
+    private Node node;
+    @XmlElement(name="portStat")
+    private List<NodeConnectorStatistics> portStats;
+
+    // To satisfy JAXB
+    @SuppressWarnings("unused")
+       private PortStatistics() {
+    }
+
+    public PortStatistics(Node node, List<NodeConnectorStatistics> portStats) {
+        super();
+        this.node = node;
+        this.portStats = portStats;
+    }
+
+    public Node getNode() {
+        return node;
+    }
+
+    public void setNode(Node node) {
+        this.node = node;
+    }
+
+    public List<NodeConnectorStatistics> getPortStats() {
+        return portStats;
+    }
+
+    public void setFlowStats(List<NodeConnectorStatistics> portStats) {
+        this.portStats = portStats;
+    }
+
+}
diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java
new file mode 100644 (file)
index 0000000..651cb2d
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statistics.northbound;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.containermanager.IContainerManager;
+
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.statisticsmanager.IStatisticsManager;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+/**
+ * Northbound APIs that returns various Statistics exposed by the Southbound plugins such as Openflow.
+ * 
+ * <br><br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default. Administrator can enable it in tomcat-server.xml after adding 
+ * a proper keystore / SSL certificate from a trusted authority.<br>
+ * More info : http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
+ *
+ */
+@Path("/")
+public class StatisticsNorthbound {
+
+    private IStatisticsManager getStatisticsService(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new ServiceUnavailableException("Container "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        boolean found = false;
+        List<String> containerNames = containerManager.getContainerNames();
+        for (String cName : containerNames) {
+            if (cName.trim().equalsIgnoreCase(containerName.trim())) {
+                found = true;
+            }
+        }
+
+        if (found == false) {
+            throw new ResourceNotFoundException(containerName + " "
+                    + RestMessages.NOCONTAINER.toString());
+        }
+
+        IStatisticsManager statsManager = (IStatisticsManager) ServiceHelper
+                .getInstance(IStatisticsManager.class, containerName, this);
+
+        if (statsManager == null) {
+            throw new ServiceUnavailableException("Statistics "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        return statsManager;
+    }
+
+    /**
+     * Returns a list of all Flow Statistics from all the Nodes.
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default". 
+     * @return List of FlowStatistics from all the Nodes
+     */
+
+    @Path("/{containerName}/flowstats")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(AllFlowStatistics.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public AllFlowStatistics getFlowStatistics(
+            @PathParam("containerName") String containerName) {
+        IStatisticsManager statisticsManager = getStatisticsService(containerName);
+        if (statisticsManager == null) {
+            throw new ServiceUnavailableException("Statistics "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ServiceUnavailableException("Switch manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        List<FlowStatistics> statistics = new ArrayList<FlowStatistics>();
+        for (Node node : switchManager.getNodes()) {
+            List<FlowOnNode> flowStats = new ArrayList<FlowOnNode>();
+
+            List<FlowOnNode> flows = statisticsManager.getFlows(node);
+            for (FlowOnNode flowOnSwitch : flows) {
+                flowStats.add(flowOnSwitch);
+            }
+            FlowStatistics stat = new FlowStatistics(node,
+                                                     flowStats);
+            statistics.add(stat);
+        }
+        return new AllFlowStatistics(statistics);
+    }
+
+    /**
+     * Returns a list of Flow Statistics for a given Node.
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default". 
+     * @param nodeType Node Type as specifid by Node class
+     * @param nodeId Node Identifier
+     * @return List of Flow Statistics for a given Node.
+     */
+    @Path("/{containerName}/flowstats/{nodeType}/{nodeId}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(FlowStatistics.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public FlowStatistics getFlowStatistics(
+            @PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId) {
+
+        handleDefaultDisabled(containerName);
+
+        IStatisticsManager statisticsManager = getStatisticsService(containerName);
+        if (statisticsManager == null) {
+            throw new ServiceUnavailableException("Statistics "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ServiceUnavailableException("Switch manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName, nodeType, nodeId);
+        return new FlowStatistics(node, statisticsManager.getFlows(node));
+    }
+
+    /**
+     * Returns a list of all the Port Statistics across all the NodeConnectors on all the Nodes.
+     *
+     * @param containerName Name of the Container. The Container name for the base controller is "default". 
+     * @return List of all the Port Statistics across all the NodeConnectors on all the Nodes.
+     */
+
+    @Path("/{containerName}/portstats")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(AllPortStatistics.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public AllPortStatistics getPortStatistics(
+            @PathParam("containerName") String containerName) {
+
+        IStatisticsManager statisticsManager = getStatisticsService(containerName);
+        if (statisticsManager == null) {
+            throw new ServiceUnavailableException("Statistics "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ServiceUnavailableException("Switch manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        List<PortStatistics> statistics = new ArrayList<PortStatistics>();
+        for (Node node : switchManager.getNodes()) {
+            List<NodeConnectorStatistics> stat = statisticsManager
+                    .getNodeConnectorStatistics(node);
+            PortStatistics portStat = new PortStatistics(node, stat);
+            statistics.add(portStat);
+        }
+        return new AllPortStatistics(statistics);
+    }
+
+    /**
+     * Returns a list of all the Port Statistics across all the NodeConnectors in a given Node.
+     *
+     * @param containerName Name of the Container. The Container name
+     * for the base controller is "default". 
+     * @param nodeType Node Type as specifid by Node class
+     * @param Node Identifier
+     * @return Returns a list of all the Port Statistics across all the NodeConnectors in a given Node.
+     */
+    @Path("/{containerName}/portstats/{nodeType}/{nodeId}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(PortStatistics.class)
+    @StatusCodes( {
+            @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The containerName is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+    public PortStatistics getPortStatistics(
+            @PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId) {
+
+        handleDefaultDisabled(containerName);
+
+        IStatisticsManager statisticsManager = getStatisticsService(containerName);
+        if (statisticsManager == null) {
+            throw new ServiceUnavailableException("Statistics "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ServiceUnavailableException("Switch manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Node node = handleNodeAvailability(containerName,
+                                           nodeType, nodeId);
+        return new PortStatistics(node, statisticsManager
+                .getNodeConnectorStatistics(node));
+    }
+
+    private void handleDefaultDisabled(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new InternalServerErrorException(RestMessages.INTERNALERROR
+                    .toString());
+        }
+        if (containerName.equals(GlobalConstants.DEFAULT.toString())
+                && containerManager.hasNonDefaultContainer()) {
+            throw new ResourceConflictException(RestMessages.DEFAULTDISABLED
+                    .toString());
+        }
+    }
+
+    private Node handleNodeAvailability(String containerName, String nodeType,
+                                        String nodeId) {
+
+        Node node = Node.fromString(nodeType, nodeId);
+        if (node == null) {
+            throw new ResourceNotFoundException(nodeId + " : "
+                    + RestMessages.NONODE.toString());
+        }
+
+        ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+
+        if (sm == null) {
+            throw new ServiceUnavailableException("Switch Manager "
+                    + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        if (!sm.getNodes().contains(node)) {
+            throw new ResourceNotFoundException(node.toString() + " : "
+                    + RestMessages.NONODE.toString());
+        }
+        return node;
+    }
+
+}
diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthboundRSApplication.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..d387407
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statistics.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * Instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class StatisticsNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(StatisticsNorthbound.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/statistics/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/statistics/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/statistics/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/statistics/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/statistics/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..768aafb
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN,ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/statistics/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/statistics/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..2b9f75f
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  <servlet>
+    <servlet-name>JAXRSStatistics</servlet-name>
+    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.statistics.northbound.StatisticsNorthboundRSApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JAXRSStatistics</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
\ No newline at end of file
diff --git a/opendaylight/northbound/subnets/enunciate.xml b/opendaylight/northbound/subnets/enunciate.xml
new file mode 100644 (file)
index 0000000..275e4d0
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/subnet"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/subnets/pom.xml b/opendaylight/northbound/subnets/pom.xml
new file mode 100644 (file)
index 0000000..9efdabe
--- /dev/null
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>subnets.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>clustering.services</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>configuration</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>switchmanager</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+            </Export-Package>
+            <Import-Package>
+                         org.opendaylight.controller.sal.core,
+                         org.opendaylight.controller.sal.utils,
+                         org.opendaylight.controller.containermanager,
+                         org.opendaylight.controller.switchmanager,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              com.sun.jersey.spi.container.servlet,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              org.slf4j,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Export-Package>
+            </Export-Package>
+            <Web-ContextPath>/one/nb/v2/subnet</Web-ContextPath>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetConfigs.java b/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetConfigs.java
new file mode 100644 (file)
index 0000000..60062ed
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.subnets.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class SubnetConfigs {
+       @XmlElement
+       List<SubnetConfig> subnetConfig;
+       //To satisfy JAXB
+       private SubnetConfigs() {
+               
+       }
+       
+       public SubnetConfigs(List<SubnetConfig> subnetConfig) {
+               this.subnetConfig = subnetConfig;
+       }
+
+       public List<SubnetConfig> getSubnetConfig() {
+               return subnetConfig;
+       }
+
+       public void setSubnetConfig(List<SubnetConfig> subnetConfig) {
+               this.subnetConfig = subnetConfig;
+       }
+}
diff --git a/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthboundJAXRS.java b/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthboundJAXRS.java
new file mode 100644 (file)
index 0000000..1e53362
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.subnets.northbound;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("/")
+public class SubnetsNorthboundJAXRS {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(SubnetsNorthboundJAXRS.class);
+
+    /**
+     * List all the subnets in a given container
+     *
+     * @param containerName container in which we want to query the subnets
+     *
+     * @return a List of SubnetConfig
+     */
+    @Path("/{containerName}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( { @ResponseCode(code = 404, condition = "The containerName passed was not found") })
+    @TypeHint(SubnetConfigs.class)
+    public SubnetConfigs listSubnets(
+            @PathParam("containerName") String containerName) {
+        ISwitchManager switchManager = null;
+        switchManager = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+        return new SubnetConfigs(switchManager.getSubnetsConfigList());
+    }
+
+    /**
+     * List the configuration of a subnet in a given container
+     *
+     * @param containerName container in which we want to query the
+     * subnet
+     * @param subnetName of the subnet being queried
+     *
+     * @return a SubnetConfig
+     */
+    @Path("/{containerName}/{subnetName}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+            @ResponseCode(code = 404, condition = "The containerName passed was not found"),
+            @ResponseCode(code = 404, condition = "Subnet does not exist") })
+    @TypeHint(SubnetConfig.class)
+    public SubnetConfig listSubnet(
+            @PathParam("containerName") String containerName,
+            @PathParam("subnetName") String subnetName) {
+        ISwitchManager switchManager = null;
+        switchManager = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+        SubnetConfig res = switchManager.getSubnetConfig(subnetName);
+        if (res == null) {
+            throw new ResourceNotFoundException(RestMessages.NOSUBNET
+                    .toString());
+        } else {
+            return res;
+        }
+    }
+
+    /**
+     * Add/Update a subnet to a container
+     *
+     * @param containerName container in which we want to add/update the
+     * subnet
+     * @param subnetName that has to be added
+     * @param subnet pair default gateway IP/mask that identify the
+     * subnet being added modified
+     *
+     */
+    @Path("/{containerName}/{subnetName}")
+    @POST
+    @StatusCodes( {
+            @ResponseCode(code = 404, condition = "The containerName passed was not found"),
+            @ResponseCode(code = 404, condition = "Invalid Data passed"),
+            @ResponseCode(code = 201, condition = "Subnet added/modified"),
+            @ResponseCode(code = 500, condition = "Addition of subnet failed") })
+    public Response addSubnet(@PathParam("containerName") String containerName,
+            @PathParam("subnetName") String subnetName,
+            @QueryParam("subnet") String subnet) {
+        if (subnetName == null) {
+            throw new ResourceNotFoundException(RestMessages.INVALIDDATA.toString());
+        }
+        if (subnet == null) {
+            throw new ResourceNotFoundException(RestMessages.INVALIDDATA.toString());
+        }
+        ISwitchManager switchManager = null;
+        switchManager = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER.toString());
+        }
+
+        SubnetConfig cfgObject = new SubnetConfig(subnetName, subnet,
+                new ArrayList<String>(0));
+        Status status = switchManager.addSubnet(cfgObject);
+        if (status.isSuccess()) {
+            return Response.status(Response.Status.CREATED).build();
+        }
+        throw new InternalServerErrorException(status.getDescription());
+    }
+
+    /**
+     * Delete a subnet from a container
+     *
+     * @param containerName container in which we want to delete the
+     * subnet by name
+     * @param subnetName of the subnet to be remove.
+     *
+     */
+    @Path("/{containerName}/{subnetName}")
+    @DELETE
+    @StatusCodes( {
+            @ResponseCode(code = 404, condition = "The containerName passed was not found"),
+            @ResponseCode(code = 500, condition = "Removal of subnet failed") })
+    public Response removeSubnet(
+            @PathParam("containerName") String containerName,
+            @PathParam("subnetName") String subnetName) {
+        if (subnetName == null) {
+            throw new ResourceNotFoundException(RestMessages.INVALIDDATA
+                    .toString());
+        }
+        ISwitchManager switchManager = null;
+        switchManager = (ISwitchManager) ServiceHelper.getInstance(
+                ISwitchManager.class, containerName, this);
+        if (switchManager == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+        Status status = switchManager.removeSubnet(subnetName);
+        if (status.isSuccess()) {
+            return Response.status(Response.Status.OK).build();
+        }
+        throw new InternalServerErrorException(status.getDescription());
+    }
+
+    /* /\** */
+    /*  * */
+    /*  * Add or remove switch ports to a subnet */
+    /*  * */
+    /*  * POST subnets/green/sw */
+    /*  * */
+    /*  * @param model */
+    /*  * @param containerName */
+    /*  * @param name */
+    /*  * @param subnet: the subnet name name */
+    /*  * @param switchports: datapath ID/port list => xx:xx:xx:xx:xx:xx:xx:xx/a,b,c-m,r-t,y */
+    /*  * @return */
+    /*  *\/ */
+    /* @RequestMapping(value = "/{containerName}/{name}", method = RequestMethod.POST) */
+    /* public View addSwitchports(Map<String, Object> model, */
+    /*         @PathVariable(value = "containerName") String containerName, */
+    /*         @PathVariable(value = "name") String name, */
+    /*         @RequestParam(value = "nodeports") String nodePorts, */
+    /*         @RequestParam(value = "action") String action) { */
+
+    /*     checkDefaultDisabled(containerName); */
+
+    /*     ISwitchManager switchManager = null; */
+    /*     try { */
+    /*         BundleContext bCtx = FrameworkUtil.getBundle(this.getClass()) */
+    /*                 .getBundleContext(); */
+
+    /*         ServiceReference[] services = bCtx.getServiceReferences( */
+    /*                 ISwitchManager.class.getName(), "(containerName=" */
+    /*                         + containerName + ")"); */
+
+    /*         if (services != null) { */
+    /*             switchManager = (ISwitchManager) bCtx.getService(services[0]); */
+    /*             logger.debug("Switch manager reference is:" + switchManager); */
+    /*         } */
+    /*     } catch (Exception e) { */
+    /*         logger.error("Switch Manager reference is NULL"); */
+    /*     } */
+
+    /*     checkContainerExists(switchManager); */
+
+    /*     String ret; */
+    /*     if (action.equals("add")) { */
+    /*         ret = switchManager.addPortsToSubnet(name, nodePorts); */
+    /*     } else if (action.equals("remove")) { */
+    /*         ret = switchManager.removePortsFromSubnet(name, nodePorts); */
+    /*     } else { */
+    /*         throw new UnsupportedMediaTypeException(RestMessages.UNKNOWNACTION */
+    /*                 .toString() */
+    /*                 + ": " + action); */
+    /*     } */
+
+    /*     return returnViewOrThrowConflicEx(model, ret); */
+    /* } */
+}
diff --git a/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthboundRSApplication.java b/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..5efe6ba
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.subnets.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * Instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class SubnetsNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(SubnetsNorthboundJAXRS.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/subnets/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/subnets/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/subnets/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/subnets/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/subnets/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..768aafb
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN,ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/subnets/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/subnets/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..b0fc9ee
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  <servlet>
+    <servlet-name>JAXRSSubnets</servlet-name>
+    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.subnets.northbound.SubnetsNorthboundRSApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JAXRSSubnets</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
diff --git a/opendaylight/northbound/switchmanager/enunciate.xml b/opendaylight/northbound/switchmanager/enunciate.xml
new file mode 100644 (file)
index 0000000..5f8f9c9
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+  
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/switch"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="Switch Manager REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/switchmanager/pom.xml b/opendaylight/northbound/switchmanager/pom.xml
new file mode 100644 (file)
index 0000000..55c836d
--- /dev/null
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>switchmanager.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>    
+    
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.containermanager,
+              org.opendaylight.controller.switchmanager,
+              org.apache.commons.lang3.tuple,
+              org.apache.commons.logging,              
+              com.sun.jersey.spi.container.servlet,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              org.slf4j,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Web-ContextPath>/one/nb/v2/switch</Web-ContextPath>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>
+  
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency> 
+
+    <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+        
+       <dependency>
+               <groupId>org.springframework</groupId>
+               <artifactId>spring-web</artifactId>
+               <version>${spring.version}</version>
+               <scope>provided</scope>
+       </dependency>
+
+       <dependency>
+               <groupId>org.springframework.security</groupId>
+               <artifactId>spring-security-config</artifactId>
+               <version>${spring-security.version}</version>
+               <scope>provided</scope>
+       </dependency>
+       
+       <dependency>
+               <groupId>org.springframework.security</groupId>
+               <artifactId>spring-security-web</artifactId>
+               <version>${spring-security.version}</version>
+               <scope>provided</scope>
+       </dependency>
+        
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+    
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectorProperties.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectorProperties.java
new file mode 100644 (file)
index 0000000..4e67fc4
--- /dev/null
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.northbound;
+
+import java.util.Set;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+
+/**
+ * The class describes set of properties attached to a node connector
+ */
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class NodeConnectorProperties {
+    @XmlElement
+    private NodeConnector nodeconnector;
+    @XmlElementRef
+    @XmlElementWrapper
+    private Set<Property> properties;
+
+    // JAXB required constructor
+    private NodeConnectorProperties() {
+        this.nodeconnector = null;
+        this.properties = null;
+    }
+
+    public NodeConnectorProperties(NodeConnector nodeconnector, Set<Property> properties) {
+        this.nodeconnector = nodeconnector;
+        this.properties = properties;
+    }
+
+    public Set<Property> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(Set<Property> properties) {
+        this.properties = properties;
+    }
+
+    public NodeConnector getNodeConnector() {
+        return nodeconnector;
+    }
+
+    public void setNodeConnector(NodeConnector nodeconnector) {
+        this.nodeconnector = nodeconnector;
+    }
+}
diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeConnectors.java
new file mode 100644 (file)
index 0000000..ac9be4d
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class NodeConnectors {
+       @XmlElement
+       List<NodeConnectorProperties> nodeConnectorProperties;
+       //To satisfy JAXB
+       private NodeConnectors() {
+               
+       }
+       
+       public NodeConnectors(List<NodeConnectorProperties> nodeConnectorProperties) {
+               this.nodeConnectorProperties = nodeConnectorProperties;
+       }
+
+       public List<NodeConnectorProperties> getNodeConnectorProperties() {
+               return nodeConnectorProperties;
+       }
+
+       public void setNodeConnectorProperties(List<NodeConnectorProperties> nodeConnectorProperties) {
+               this.nodeConnectorProperties = nodeConnectorProperties;
+       }
+}
diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeProperties.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/NodeProperties.java
new file mode 100644 (file)
index 0000000..979a8e9
--- /dev/null
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.northbound;
+
+import java.util.Set;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Property;
+
+/**
+ * The class describes set of properties attached to a node
+ */
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class NodeProperties {
+    @XmlElement
+    private Node node;
+    @XmlElementRef
+    @XmlElementWrapper
+    private Set<Property> properties;
+
+    // JAXB required constructor
+    private NodeProperties() {
+        this.node = null;
+        this.properties = null;
+    }
+
+    public NodeProperties(Node node, Set<Property> properties) {
+        this.node = node;
+        this.properties = properties;
+    }
+
+    public Set<Property> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(Set<Property> properties) {
+        this.properties = properties;
+    }
+
+    public Node getNode() {
+        return node;
+    }
+
+    public void setNode(Node node) {
+        this.node = node;
+    }
+}
diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/Nodes.java
new file mode 100644 (file)
index 0000000..07d7b7c
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class Nodes {
+       @XmlElement
+       List<NodeProperties> nodeProperties;
+       //To satisfy JAXB
+       private Nodes() {
+               
+       }
+       
+       public Nodes(List<NodeProperties> nodeProperties) {
+               this.nodeProperties = nodeProperties;
+       }
+
+       public List<NodeProperties> getNodeProperties() {
+               return nodeProperties;
+       }
+
+       public void setNodeProperties(List<NodeProperties> nodeProperties) {
+               this.nodeProperties = nodeProperties;
+       }
+}
diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java
new file mode 100644 (file)
index 0000000..c26d4f2
--- /dev/null
@@ -0,0 +1,549 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.northbound;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.containermanager.IContainerManager;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
+import org.opendaylight.controller.sal.core.MacAddress;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+/**
+ * The class provides Northbound REST APIs to access the nodes, node connectors
+ * and their properties.
+ * 
+ */
+
+@Path("/")
+public class SwitchNorthbound {
+
+       private ISwitchManager getIfSwitchManagerService(String containerName) {
+               IContainerManager containerManager = (IContainerManager) ServiceHelper
+               .getGlobalInstance(IContainerManager.class, this);
+               if (containerManager == null) {
+                       throw new ServiceUnavailableException("Container "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               boolean found = false;
+               List<String> containerNames = containerManager.getContainerNames();
+               for (String cName : containerNames) {
+                       if (cName.trim().equalsIgnoreCase(containerName.trim())) {
+                               found = true;
+                       }
+               }
+
+               if (found == false) {
+                       throw new ResourceNotFoundException(containerName + " "
+                                       + RestMessages.NOCONTAINER.toString());
+               }
+
+               ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(
+                               ISwitchManager.class, containerName, this);
+
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               return switchManager;
+       }
+
+       /**
+        * 
+        * Retrieve a list of all the nodes and their properties in the network
+        * 
+        * @param containerName The container for which we want to retrieve the list
+        * @return A list of Pair each pair represents a
+        *         {@link org.opendaylight.controller.sal.core.Node} and Set of
+        *         {@link org.opendaylight.controller.sal.core.Property} attached to
+        *         it.
+        */
+       @Path("/{containerName}/nodes")
+       @GET
+       @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @TypeHint(Nodes.class)
+       @StatusCodes( {
+               @ResponseCode(code = 200, condition = "Operation successful"),
+               @ResponseCode(code = 404, condition = "The containerName is not found"),
+               @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+               public Nodes getNodes(
+                               @PathParam("containerName") String containerName) {
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               List<NodeProperties> res = new ArrayList<NodeProperties>();
+               Set<Node> nodes = switchManager.getNodes();
+               if (nodes == null) {
+                       return null;
+               }
+
+               byte[] controllerMac = switchManager.getControllerMAC();
+               for (Node node : nodes) {
+                       Map<String, Property> propMap = switchManager.getNodeProps(node);
+                       if (propMap == null) {
+                               continue;
+                       }
+                       Set<Property> props = new HashSet<Property>(propMap.values());
+                       
+                       byte[] nodeMac = switchManager.getNodeMAC(node);
+                       Property macAddr = new MacAddress(controllerMac, nodeMac);
+                       props.add(macAddr);
+                       
+                       NodeProperties nodeProps = new NodeProperties(node, props);
+                       res.add(nodeProps);
+               }
+
+               return new Nodes(res);
+       }
+
+    /**
+     * Add a Name/Tier property to a node
+     *
+     * @param containerName Name of the Container
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier as specified by {@link org.opendaylight.controller.sal.core.Node}
+     * @param propName Name of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @param propValue Value of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return Response as dictated by the HTTP Response Status code
+     */
+
+    @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propName}/{propValue}")
+    @PUT
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(Response.class)
+    @StatusCodes( {
+           @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
+    public Response addNodeProperty(@PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId,
+            @PathParam("propName") String propName,
+            @PathParam("propValue") String propValue) {
+
+        handleDefaultDisabled(containerName);
+
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+        handleNodeAvailability(containerName, nodeType, nodeId);
+               Node node = Node.fromString(nodeId);
+        
+               Property prop = switchManager.createProperty(propName, propValue);
+               if (prop == null) {
+                       throw new ResourceNotFoundException(
+                                       RestMessages.INVALIDDATA.toString());
+               }
+               
+        switchManager.setNodeProp(node, prop);
+        return Response.status(Response.Status.CREATED).build();
+    }
+
+    /**
+     * Delete a property of a node
+     *
+     * @param containerName Name of the Container
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier as specified by {@link org.opendaylight.controller.sal.core.Node}
+     * @param propertyName Name of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return Response as dictated by the HTTP Response Status code
+     */
+
+    @Path("/{containerName}/node/{nodeType}/{nodeId}/property/{propertyName}")
+    @DELETE
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+           @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
+    public Response deleteNodeProperty(@PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId,
+            @PathParam("propertyName") String propertyName) {
+
+        handleDefaultDisabled(containerName);
+
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+        handleNodeAvailability(containerName, nodeType, nodeId);
+               Node node = Node.fromString(nodeId);
+        
+        Status ret = switchManager.removeNodeProp(node, propertyName);
+        if (ret.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new ResourceNotFoundException(ret.getDescription());
+    }
+
+       /**
+        * 
+        * Retrieve a list of all the node connectors and their properties in a given node
+        * 
+        * @param containerName The container for which we want to retrieve the list
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier as specified by {@link org.opendaylight.controller.sal.core.Node}
+        * @return A List of Pair each pair represents a
+        *         {@link org.opendaylight.controller.sal.core.NodeConnector} and
+        *         its corresponding
+        *         {@link org.opendaylight.controller.sal.core.Property} attached to
+        *         it.
+        */
+       @Path("/{containerName}/node/{nodeType}/{nodeId}")
+       @GET
+       @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @TypeHint(NodeConnectors.class)
+       @StatusCodes( {
+               @ResponseCode(code = 200, condition = "Operation successful"),
+               @ResponseCode(code = 404, condition = "The containerName is not found"),
+               @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+               public NodeConnectors getNodeConnectors(
+                               @PathParam("containerName") String containerName,
+                   @PathParam("nodeType") String nodeType,
+                               @PathParam("nodeId") String nodeId) {
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               handleNodeAvailability(containerName, nodeType,nodeId);
+               Node node = Node.fromString(nodeId);
+
+               List<NodeConnectorProperties> res = new ArrayList<NodeConnectorProperties>();
+               Set<NodeConnector> ncs = switchManager.getNodeConnectors(node);
+               if (ncs == null) {
+                       return null;
+               }
+
+               for (NodeConnector nc : ncs) {
+                       Map<String, Property> propMap = switchManager.getNodeConnectorProps(nc);
+                       if (propMap == null) {
+                               continue;
+                       }
+                       Set<Property> props = new HashSet<Property>(propMap.values());
+                       NodeConnectorProperties ncProps = new NodeConnectorProperties(nc, props);
+                       res.add(ncProps);
+               }
+
+               return new NodeConnectors(res);
+       }
+
+    /**
+     * Add a Name/Bandwidth property to a node connector
+     *
+     * @param containerName Name of the Container 
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier as specified by {@link org.opendaylight.controller.sal.core.Node}
+     * @param nodeConnectorType Type of the node connector being programmed
+     * @param nodeConnectorId NodeConnector Identifier as specified by {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param propName Name of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @param propValue Value of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return Response as dictated by the HTTP Response Status code
+     */
+
+    @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propName}/{propValue}")
+    @PUT
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+           @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
+    public Response addNodeConnectorProperty(@PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId,
+            @PathParam("nodeConnectorType") String nodeConnectorType,
+            @PathParam("nodeConnectorId") String nodeConnectorId,
+            @PathParam("propName") String propName,
+            @PathParam("propValue") String propValue) {
+
+        handleDefaultDisabled(containerName);
+
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               handleNodeAvailability(containerName, nodeType, nodeId);
+               Node node = Node.fromString(nodeId);
+
+               handleNodeConnectorAvailability(containerName, node, nodeConnectorType, nodeConnectorId);
+               NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorId, node);
+        
+               Property prop = switchManager.createProperty(propName, propValue);
+               if (prop == null) {
+                       throw new ResourceNotFoundException(
+                                       RestMessages.INVALIDDATA.toString());
+               }
+               
+               Status ret = switchManager.addNodeConnectorProp(nc, prop);
+        if (ret.isSuccess()) {
+            return Response.status(Response.Status.CREATED).build();
+        }
+        throw new InternalServerErrorException(ret.getDescription());
+    }
+
+    /**
+     * Delete a property of a node connector
+     *
+     * @param containerName Name of the Container
+     * @param nodeType Type of the node being programmed
+     * @param nodeId Node Identifier as specified by {@link org.opendaylight.controller.sal.core.Node}
+     * @param nodeConnectorType Type of the node connector being programmed
+     * @param nodeConnectorId NodeConnector Identifier as specified by {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param propertyName Name of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return Response as dictated by the HTTP Response Status code
+     */
+
+    @Path("/{containerName}/nodeconnector/{nodeType}/{nodeId}/{nodeConnectorType}/{nodeConnectorId}/property/{propertyName}")
+    @DELETE
+    @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes( {
+           @ResponseCode(code = 200, condition = "Operation successful"),
+            @ResponseCode(code = 404, condition = "The Container Name or nodeId or configuration name is not found"),
+            @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
+    public Response deleteNodeConnectorProperty(@PathParam("containerName") String containerName,
+            @PathParam("nodeType") String nodeType,
+            @PathParam("nodeId") String nodeId,
+            @PathParam("nodeConnectorType") String nodeConnectorType,
+            @PathParam("nodeConnectorId") String nodeConnectorId,
+            @PathParam("propertyName") String propertyName) {
+
+        handleDefaultDisabled(containerName);
+
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               handleNodeAvailability(containerName, nodeType, nodeId);
+               Node node = Node.fromString(nodeId);
+               
+               handleNodeConnectorAvailability(containerName, node, nodeConnectorType, nodeConnectorId);
+               NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorId, node);
+        
+        Status ret = switchManager.removeNodeConnectorProp(nc, propertyName);
+        if (ret.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new ResourceNotFoundException(ret.getDescription());
+    }
+
+/*    *//**
+     * Retrieve a list of Span ports that were configured previously.
+     *
+     * @param containerName Name of the Container 
+     * @return list of {@link org.opendaylight.controller.switchmanager.SpanConfig} resources
+     *//*
+       @Path("/span-config/{containerName}")
+       @GET
+       @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes( {
+               @ResponseCode(code = 200, condition = "Operation successful"),
+               @ResponseCode(code = 404, condition = "The containerName is not found"),
+               @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+               public List<SpanConfig> getSpanConfigList(@PathParam("containerName") String containerName) {
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               return switchManager.getSpanConfigList();
+       }
+
+    *//**
+     * Add a span configuration
+     *
+     * @param containerName Name of the Container 
+     * @param config {@link org.opendaylight.controller.switchmanager.SpanConfig} in JSON or XML format
+     * @return Response as dictated by the HTTP Response Status code
+     *//*
+       @Path("/span-config/{containerName}")
+       @PUT
+       @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes( {
+               @ResponseCode(code = 200, condition = "Operation successful"),
+               @ResponseCode(code = 404, condition = "The containerName is not found"),
+               @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+               public Response addSpanConfig(@PathParam("containerName") String containerName,
+                   @TypeHint(SubnetConfig.class) JAXBElement<SpanConfig> config) {
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               String ret = switchManager.addSpanConfig(config.getValue());
+        if (ret.equals(ReturnString.SUCCESS.toString())) {
+            return Response.status(Response.Status.CREATED).build();
+        }
+        throw new InternalServerErrorException(ret);
+       }
+
+    *//**
+     * Delete a span configuration
+     *
+     * @param containerName Name of the Container 
+     * @param config {@link org.opendaylight.controller.switchmanager.SpanConfig} in JSON or XML format
+     * @return Response as dictated by the HTTP Response Status code
+     *//*
+       @Path("/span-config/{containerName}")
+       @DELETE
+       @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes( {
+               @ResponseCode(code = 200, condition = "Operation successful"),
+               @ResponseCode(code = 404, condition = "The containerName is not found"),
+               @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+               public Response deleteSpanConfig(@PathParam("containerName") String containerName,
+                   @TypeHint(SubnetConfig.class) JAXBElement<SpanConfig> config) {
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               String ret = switchManager.removeSpanConfig(config.getValue());
+        if (ret.equals(ReturnString.SUCCESS.toString())) {
+            return Response.ok().build();
+        }
+        throw new ResourceNotFoundException(ret);
+       }
+*/
+    
+    /**
+     * Save the current switch configurations
+     *
+     * @param containerName Name of the Container 
+     * @return Response as dictated by the HTTP Response Status code
+     */
+       @Path("/{containerName}/switch-config")
+       @POST
+       @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes( {
+               @ResponseCode(code = 200, condition = "Operation successful"),
+               @ResponseCode(code = 404, condition = "The containerName is not found"),
+               @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
+               public Response saveSwitchConfig(@PathParam("containerName") String containerName) {
+               ISwitchManager switchManager = (ISwitchManager) getIfSwitchManagerService(containerName);
+               if (switchManager == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               Status ret = switchManager.saveSwitchConfig();
+        if (ret.isSuccess()) {
+            return Response.ok().build();
+        }
+        throw new InternalServerErrorException(ret.getDescription());
+       }
+
+    private void handleDefaultDisabled(String containerName) {
+        IContainerManager containerManager = (IContainerManager) ServiceHelper
+                .getGlobalInstance(IContainerManager.class, this);
+        if (containerManager == null) {
+            throw new InternalServerErrorException(RestMessages.INTERNALERROR
+                    .toString());
+        }
+        if (containerName.equals(GlobalConstants.DEFAULT.toString())
+                && containerManager.hasNonDefaultContainer()) {
+            throw new ResourceConflictException(RestMessages.DEFAULTDISABLED
+                    .toString());
+        }
+    }
+
+    private Node handleNodeAvailability(String containerName, String nodeType,
+               String nodeId) {
+
+       Node node = Node.fromString(nodeType, nodeId);
+       if (node == null) {
+               throw new ResourceNotFoundException(nodeId + " : "
+                               + RestMessages.NONODE.toString());
+       }
+
+       ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
+                       ISwitchManager.class, containerName, this);
+
+       if (sm == null) {
+               throw new ServiceUnavailableException("Switch Manager "
+                               + RestMessages.SERVICEUNAVAILABLE.toString());
+       }
+
+       if (!sm.getNodes().contains(node)) {
+               throw new ResourceNotFoundException(node.toString() + " : "
+                               + RestMessages.NONODE.toString());
+       }
+       return node;
+    }
+
+       private void handleNodeConnectorAvailability(String containerName,
+                       Node node, String nodeConnectorType, String nodeConnectorId) {
+
+               NodeConnector nc = NodeConnector.fromStringNoNode(nodeConnectorType,
+                               nodeConnectorId, node);
+               if (nc == null) {
+                       throw new ResourceNotFoundException(nc + " : "
+                                       + RestMessages.NORESOURCE.toString());
+               }
+               
+               ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
+                               ISwitchManager.class, containerName, this);
+
+               if (sm == null) {
+                       throw new ServiceUnavailableException("Switch Manager "
+                                       + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               if (!sm.getNodeConnectors(node).contains(nc)) {
+                       throw new ResourceNotFoundException(nc.toString() + " : "
+                                       + RestMessages.NORESOURCE.toString());
+               }
+       }
+}
diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthboundRSApplication.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..d33c3c9
--- /dev/null
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * Instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class SwitchNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(SwitchNorthbound.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/switchmanager/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..768aafb
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN,ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..697a7e3
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  <servlet>
+    <servlet-name>JAXRSSwitchManager</servlet-name>
+    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.switchmanager.northbound.SwitchNorthboundRSApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JAXRSSwitchManager</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
\ No newline at end of file
diff --git a/opendaylight/northbound/topology/enunciate.xml b/opendaylight/northbound/topology/enunciate.xml
new file mode 100644 (file)
index 0000000..7103df9
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+  
+  <services>
+    <rest defaultRestSubcontext="/one/nb/v2/topology"/>
+  </services>
+
+  <modules>
+    <docs docsDir="rest" title="REST API" includeExampleXml="true" includeExampleJson="true"/>
+  </modules>
+</enunciate>
diff --git a/opendaylight/northbound/topology/pom.xml b/opendaylight/northbound/topology/pom.xml
new file mode 100644 (file)
index 0000000..c6963b8
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>topology.northbound</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+     <plugins>
+      <plugin>
+        <groupId>org.codehaus.enunciate</groupId>
+        <artifactId>maven-enunciate-plugin</artifactId>
+        <version>${enunciate.version}</version>
+        <dependencies>
+          <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <version>2.3.6</version>
+          <extensions>true</extensions>
+          <configuration>
+          <instructions>
+            <Export-Package>
+            </Export-Package>
+            <Import-Package>
+              org.opendaylight.controller.containermanager,
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.sal.packet.address,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.switchmanager,
+              org.opendaylight.controller.topologymanager,
+              com.sun.jersey.spi.container.servlet,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind.annotation,
+              org.slf4j,
+              com.sun.jersey.spi.spring.container.servlet,
+              org.springframework.web.context,
+              org.springframework.web,
+              org.springframework.web.servlet,
+              org.springframework.web.filter,
+              org.springframework.security.config,
+              org.springframework.security.web.authentication,
+              org.springframework.security.web.authentication.www,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Web-ContextPath>/one/nb/v2/topology</Web-ContextPath>
+          </instructions>
+          </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+      <version>${enunciate.version}</version>
+    </dependency>
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>containermanager</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>commons.northbound</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>sal</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>topologymanager</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>com.sun.jersey.jersey-servlet</artifactId>
+      <version>1.17-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/EdgeProperties.java b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/EdgeProperties.java
new file mode 100644 (file)
index 0000000..b8be7c1
--- /dev/null
@@ -0,0 +1,59 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topology.northbound;
+
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Property;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class EdgeProperties {
+    @XmlElement
+    private Edge edge;
+    @XmlElementRef
+    @XmlElementWrapper
+    private Set<Property> properties;
+
+    // JAXB required constructor
+    private EdgeProperties() {
+        this.edge = null;
+        this.properties = null;
+    }
+
+    public EdgeProperties(Edge e, Set<Property> properties) {
+        this.edge = e;
+        this.properties = properties;
+    }
+
+    public Set<Property> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(Set<Property> properties) {
+        this.properties = properties;
+    }
+
+    public Edge getEdge() {
+        return edge;
+    }
+
+    public void setEdge(Edge edge) {
+        this.edge = edge;
+    }
+}
diff --git a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/Topology.java b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/Topology.java
new file mode 100644 (file)
index 0000000..bfc86cf
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topology.northbound;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class Topology {
+       @XmlElement
+       List<EdgeProperties> edgeProperties;
+       //To satisfy JAXB
+       private Topology() {
+               
+       }
+       
+       public Topology(List<EdgeProperties> edgeProperties) {
+               this.edgeProperties = edgeProperties;
+       }
+
+       public List<EdgeProperties> getEdgeProperties() {
+               return edgeProperties;
+       }
+
+       public void setEdgeProperties(List<EdgeProperties> edgeProperties) {
+               this.edgeProperties = edgeProperties;
+       }
+}
diff --git a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java
new file mode 100644 (file)
index 0000000..00b27b0
--- /dev/null
@@ -0,0 +1,88 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topology.northbound;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Topology Northbound REST API
+ * 
+ * <br><br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default. Administrator can enable it in tomcat-server.xml after adding 
+ * a proper keystore / SSL certificate from a trusted authority.<br>
+ * More info : http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
+ */
+
+@Path("/")
+public class TopologyNorthboundJAXRS {
+    private static Logger logger = LoggerFactory
+            .getLogger(TopologyNorthboundJAXRS.class);
+
+    /**
+     *
+     * Retrieve the Topology for the
+     *
+     * @param containerName The container for which we want to retrieve the topology
+     *
+     * @return A List of EdgeProps each EdgeProp represent an Edge of
+     * the grap with the corresponding properties attached to it.
+     */
+    @Path("/{containerName}")
+    @GET
+    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(Topology.class)
+    @StatusCodes( { @ResponseCode(code = 404, condition = "The containerName passed was not found") })
+    public Topology getTopology(
+            @PathParam("containerName") String containerName) {
+        ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
+                .getInstance(ITopologyManager.class, containerName, this);
+        if (topologyManager == null) {
+            throw new ResourceNotFoundException(RestMessages.NOCONTAINER
+                    .toString());
+        }
+
+        Map<Edge, Set<Property>> topo = topologyManager.getEdges();
+        if (topo != null) {
+            List<EdgeProperties> res = new ArrayList<EdgeProperties>();
+            for (Map.Entry<Edge, Set<Property>> entry : topo.entrySet()) {
+                EdgeProperties el = new EdgeProperties(entry.getKey(), entry.getValue());
+                res.add(el);
+            }
+            return new Topology(res);
+        }
+
+        return null;
+    }
+}
diff --git a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundRSApplication.java b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundRSApplication.java
new file mode 100644 (file)
index 0000000..299bfcd
--- /dev/null
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topology.northbound;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * Instance of javax.ws.rs.core.Application used to return the classes
+ * that will be instantiated for JAXRS processing, this is necessary
+ * because the package scanning in jersey doesn't yet work in OSGi
+ * environment.
+ *
+ */
+public class TopologyNorthboundRSApplication extends Application {
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+        classes.add(TopologyNorthboundJAXRS.class);
+        return classes;
+    }
+}
diff --git a/opendaylight/northbound/topology/src/main/resources/META-INF/spring.factories b/opendaylight/northbound/topology/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/northbound/topology/src/main/resources/META-INF/spring.handlers b/opendaylight/northbound/topology/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/northbound/topology/src/main/resources/META-INF/spring.schemas b/opendaylight/northbound/topology/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/northbound/topology/src/main/resources/META-INF/spring.tooling b/opendaylight/northbound/topology/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/northbound/topology/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/northbound/topology/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/northbound/topology/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/northbound/topology/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..768aafb
--- /dev/null
@@ -0,0 +1,36 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="authenticationEntryPoint">
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN,ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="restAuthenticationFilter" position="BASIC_AUTH_FILTER" />
+       </http>
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="AuthenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="restAuthenticationFilter"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
+         <beans:property name="authenticationManager" ref="authenticationManager"/>
+         <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
+       </beans:bean>
+       
+       <beans:bean id="authenticationEntryPoint"
+         class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
+         <beans:property name="realmName" value="opendaylight"/>
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.northbound.commons.WebSecurityContextRepository" />
+
+       <beans:bean id="AuthenticationProviderWrapper"
+               class="org.opendaylight.controller.northbound.commons.AuthenticationProviderWrapper" />
+
+</beans:beans>
diff --git a/opendaylight/northbound/topology/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/topology/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..9d21599
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+  version="2.4">
+  <servlet>
+    <servlet-name>JAXRSTopology</servlet-name>
+    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.topology.northbound.TopologyNorthboundRSApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JAXRSTopology</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+<!-- Spring Security related -->
+
+       <listener>
+       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <filter>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <filter-class>
+               org.springframework.web.filter.DelegatingFilterProxy
+           </filter-class>
+       </filter>
+       
+       <filter-mapping>
+           <filter-name>springSecurityFilterChain</filter-name>
+           <url-pattern>/*</url-pattern>
+       </filter-mapping>
+</web-app>
diff --git a/opendaylight/northboundtest/unit_test_suite/pom.xml b/opendaylight/northboundtest/unit_test_suite/pom.xml
new file mode 100644 (file)
index 0000000..0a22a57
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../../opendaylight/commons/opendaylight</relativePath>
+  </parent>
+  
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>northboundtest</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.eclipse.osgi.framework.console,
+                                                       org.osgi.framework,
+                                       org.apache.felix.dm
+                                               </Import-Package>
+                                               <Export-Package>
+                                               </Export-Package>
+                                               <Bundle-Activator>
+                                         org.opendaylight.controller.northboundtest.unittestsuite.internal.Activator
+                                       </Bundle-Activator>
+                                               <Service-Component>
+                                               </Service-Component>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>    
+       </dependencies>
+</project>
diff --git a/opendaylight/northboundtest/unit_test_suite/src/main/java/org/opendaylight/controller/northboundtest/unittestsuite/internal/API3UnitTest.java b/opendaylight/northboundtest/unit_test_suite/src/main/java/org/opendaylight/controller/northboundtest/unittestsuite/internal/API3UnitTest.java
new file mode 100644 (file)
index 0000000..b9ce9ba
--- /dev/null
@@ -0,0 +1,205 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northboundtest.unittestsuite.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * This java class provides the osgi console with the commands for running the unit test scripts for the API3
+ *
+ *
+ *
+ */
+public class API3UnitTest implements CommandProvider {
+    private static final String python = "/usr/bin/python";
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---API3 Unit Test---\n");
+        help
+                .append("\t api3ut             - run the python script for the specified northbound module\n");
+        help.append("\t GET <uri>");
+        help.append("\t PUT <uri>  data1==x1 data2==x2 ...");
+        help.append("\t POST <uri>  data1==x1 data2==x2 ...");
+        help.append("\t DELETE <uri>");
+        return help.toString();
+    }
+
+    public void _api3ut(CommandInterpreter ci) {
+        boolean custom = false;
+        String target = null;
+        String module = null;
+
+        module = ci.nextArgument();
+        if (module == null) {
+            printUsage(ci);
+            return;
+        }
+
+        if (module.equals("custom")) {
+            target = ci.nextArgument();
+            custom = true;
+        } else if (module.equals("flows")) {
+            target = "flowsUnitTest.py";
+        } else if (module.equals("subnets")) {
+            target = "subnetsUnitTest.py";
+        } else if (module.equals("hosts")) {
+            target = "hostsUnitTest.py";
+        } else if (module.equals("slices")) {
+            target = "slicesUnitTest.py";
+        } else if (module.equals("tif")) {
+            target = "tifUnitTest.py";
+        } else {
+            ci.println("ERROR: Coming soon");
+        }
+
+        if (target != null) {
+            executeScript(target, custom);
+        }
+    }
+
+    private void printUsage(CommandInterpreter ci) {
+        ci.println("Usage: api3ut [<module> | custom <target>]");
+        ci
+                .println("<module>: [flows, hosts, subnets, slices, tif] (You need python-httplib2 installed)");
+        ci.println("<target>: your linux script (w/ absolute path)");
+    }
+
+    private void printStream(InputStream stream) throws IOException {
+        String line;
+        BufferedReader reader = new BufferedReader(
+                new InputStreamReader(stream));
+
+        while ((line = reader.readLine()) != null) {
+            System.out.println(line);
+        }
+    }
+
+    public void executeScript(String target, boolean custom)
+            throws RuntimeException {
+        String script = (custom) ? target : "SCRIPTS/python/" + target;
+        try {
+            Runtime runTime = Runtime.getRuntime();
+            Process process = runTime.exec(python + " " + script);
+            printStream(process.getInputStream());
+            printStream(process.getErrorStream());
+        } catch (Exception e) {
+            System.out.println("Exception!");
+            e.printStackTrace();
+        }
+    }
+
+    public void _GET(CommandInterpreter ci) {
+        parseRestRequest("GET", ci);
+    }
+
+    public void _PUT(CommandInterpreter ci) {
+        parseRestRequest("PUT", ci);
+    }
+
+    public void _DELETE(CommandInterpreter ci) {
+        parseRestRequest("DELETE", ci);
+    }
+
+    public void _POST(CommandInterpreter ci) {
+        parseRestRequest("POST", ci);
+    }
+
+    private void parseRestRequest(String action, CommandInterpreter ci) {
+        String uri, resource;
+        StringBuffer resources = new StringBuffer(" ");
+
+        uri = ci.nextArgument();
+        if (uri == null) {
+            printRestUsage(ci);
+            return;
+        }
+
+        resource = ci.nextArgument();
+        while (resource != null) {
+            resources.append(resource);
+            resources.append(" ");
+            resource = ci.nextArgument();
+        }
+
+        executeRestCall(action, uri, resources.toString());
+
+    }
+
+    private void executeRestCall(String action, String uri, String resources) {
+        String script = "SCRIPTS/python/rest_call.py";
+
+        try {
+            Runtime runTime = Runtime.getRuntime();
+            Process process = runTime.exec(python + " " + script + " " + action
+                    + " " + uri + " " + resources);
+            printStream(process.getInputStream());
+            printStream(process.getErrorStream());
+        } catch (Exception e) {
+            System.out.println("Exception!");
+            e.printStackTrace();
+        }
+    }
+
+    private void printRestUsage(CommandInterpreter ci) {
+        ci.println("Usage: GET/PUT/POST/DELETE <uri>  [<resources>]");
+        ci.println("<uri>: ex: slices/red or slices/red/flowspecs");
+        ci
+                .println("<resources>: resource==<value>,... ex: switchId==2 port==3-7");
+    }
+}
diff --git a/opendaylight/northboundtest/unit_test_suite/src/main/java/org/opendaylight/controller/northboundtest/unittestsuite/internal/Activator.java b/opendaylight/northboundtest/unit_test_suite/src/main/java/org/opendaylight/controller/northboundtest/unittestsuite/internal/Activator.java
new file mode 100644 (file)
index 0000000..71f5cfd
--- /dev/null
@@ -0,0 +1,95 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.northboundtest.unittestsuite.internal;
+
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+
+import org.apache.felix.dm.Component;
+
+public class Activator extends ComponentActivatorAbstractBase {
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        return null;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+    }
+
+    /**
+     * Method which tells how many Global implementations are
+     * supported by the bundle. This way we can tune the number of
+     * components created. This components will be created ONLY at the
+     * time of bundle startup and will be destroyed only at time of
+     * bundle destruction, this is the major difference with the
+     * implementation retrieved via getImplementations where all of
+     * them are assumed to be in a Container!
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * in Global version
+     */
+    protected Object[] getGlobalImplementations() {
+        Object[] res = { API3UnitTest.class };
+        return res;
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureGlobalInstance(Component c, Object imp) {
+        if (imp.equals(API3UnitTest.class)) {
+            // Nothing to initialize
+        }
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/pom.xml b/opendaylight/protocol_plugins/openflow/pom.xml
new file mode 100644 (file)
index 0000000..418c897
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>protocol_plugins.openflow</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.sal.action,
+              org.opendaylight.controller.sal.discovery,
+              org.opendaylight.controller.sal.topology,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.flowprogrammer,
+              org.opendaylight.controller.sal.reader,
+              org.opendaylight.controller.sal.inventory,
+              org.opendaylight.controller.sal.match,
+              org.opendaylight.controller.sal.utils,
+              org.apache.commons.lang3.builder,
+              org.apache.commons.lang3.tuple,
+              org.apache.felix.dm,
+              org.slf4j,
+              org.eclipse.osgi.framework.console,
+                         org.osgi.framework
+            </Import-Package>
+            <Export-Package>
+                         org.opendaylight.controller.protocol_plugin.openflow.internal
+            </Export-Package>
+            <Embed-Dependency>
+              org.openflow.openflowj
+            </Embed-Dependency>
+            <Embed-Transitive>
+              false
+            </Embed-Transitive>
+            <Bundle-Activator>
+              org.opendaylight.controller.protocol_plugin.openflow.internal.Activator
+            </Bundle-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.openflow.openflowj</artifactId>
+      <version>1.0.2-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IDataPacketListen.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IDataPacketListen.java
new file mode 100644 (file)
index 0000000..300967e
--- /dev/null
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * @file   IDataPacketListen.java
+ *
+ * @brief  Interface to dispatch locally in the protocol plugin the
+ * data packets, intended especially for Discovery, main difference
+ * here with the analogous of SAL is that this is Global
+ * inherently
+ *
+ */
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.packet.PacketResult;
+
+/**
+ * Interface to dispatch locally in the protocol plugin the
+ * data packets, intended especially for Discovery, main difference
+ * here with the analogous of SAL is that this is Global
+ * inherently.
+ */
+public interface IDataPacketListen {
+    /**
+     * Dispatch received data packet
+     *
+     * @param inPkt
+     *            The incoming raw packet
+     * @return Possible results for Data packet processing handler
+     */
+    public PacketResult receiveDataPacket(RawPacket inPkt);
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IDataPacketMux.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IDataPacketMux.java
new file mode 100644 (file)
index 0000000..747a8fb
--- /dev/null
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+/**
+ * @file   IDataPacketMux.java
+ *
+ * @brief  Simple wrapped interface for the IPluginInDataPacketService
+ * which will be only exported by DataPacketServices mux/demux
+ * component and will be only accessible by the openflow protocol
+ * plugin
+ */
+
+import org.opendaylight.controller.sal.packet.IPluginInDataPacketService;
+
+/**
+ * Simple wrapped interface for the IPluginInDataPacketService
+ * which will be only exported by DataPacketServices mux/demux
+ * component and will be only accessible by the openflow protocol
+ * plugin
+ */
+public interface IDataPacketMux extends IPluginInDataPacketService {
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IInventoryShimExternalListener.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IInventoryShimExternalListener.java
new file mode 100644 (file)
index 0000000..24e63df
--- /dev/null
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+/**
+ * Wrapper of Interface class that provides inventory updates locally in the
+ * protocol plugin
+ *
+ *
+ */
+public interface IInventoryShimExternalListener extends
+        IInventoryShimInternalListener {
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IInventoryShimInternalListener.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IInventoryShimInternalListener.java
new file mode 100644 (file)
index 0000000..50a4bca
--- /dev/null
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+import java.util.Set;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+
+/**
+ * Interface class that provides inventory updates to inventory listeners
+ * within the protocol plugin
+ *
+ *
+ */
+public interface IInventoryShimInternalListener {
+    /**
+     * Updates node and its properties
+     *
+     * @param node                     {@link org.opendaylight.controller.sal.core.Node} being updated
+     * @param type             {@link org.opendaylight.controller.sal.core.UpdateType}
+     * @param props            set of {@link org.opendaylight.controller.sal.core.Property} such as
+     *                                                 {@link org.opendaylight.controller.sal.core.Name} and/or
+     *                                                 {@link org.opendaylight.controller.sal.core.Tier} etc.
+     */
+    public void updateNode(Node node, UpdateType type, Set<Property> props);
+
+    /**
+     * Updates node connector and its properties
+     *
+     * @param nodeConnector    {@link org.opendaylight.controller.sal.core.NodeConnector} being updated
+     * @param type             {@link org.opendaylight.controller.sal.core.UpdateType}
+     * @param props            set of {@link org.opendaylight.controller.sal.core.Property} such as
+     *                                                 {@link org.opendaylight.controller.sal.core.Name} and/or
+     *                                                 {@link org.opendaylight.controller.sal.core.State} etc.
+     */
+    public void updateNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props);
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IOFInventoryService.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IOFInventoryService.java
new file mode 100644 (file)
index 0000000..f46f24d
--- /dev/null
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+import java.util.Set;
+
+import org.opendaylight.controller.sal.core.Property;
+
+/**
+ *
+ * Interface for Inventory Service visible inside the protocol plugin only
+ *
+ *
+ *
+ */
+public interface IOFInventoryService {
+    /**
+     * Tell Inventory Service a property has been updated
+     * for the specified switch with the specified value
+     *
+     * @param switchId
+     */
+    public void updateSwitchProperty(Long switchId, Set<Property> propertySet);
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IOFStatisticsManager.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IOFStatisticsManager.java
new file mode 100644 (file)
index 0000000..36bed4f
--- /dev/null
@@ -0,0 +1,100 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+import java.util.List;
+
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+
+/**
+ * Interface to expose the openflow statistics collected on the switches
+ */
+public interface IOFStatisticsManager {
+    /**
+     * Return all the statistics for all the flows present on the specified switch
+     *
+        * @param switchId the openflow datapath id
+        * @return      the list of openflow statistics
+        */
+    List<OFStatistics> getOFFlowStatistics(Long switchId);
+
+    /**
+     * Return all the statistics for all the flows present on the specified switch
+     *
+     * @param switchId the openflow datapath id
+     * @param ofMatch the openflow match to query. If null, the query is intended for all the flows
+     * @return the list of openflow statistics
+     */
+    List<OFStatistics> getOFFlowStatistics(Long switchId, OFMatch ofMatch);
+
+    /**
+     * Return the description statistics for the specified switch.
+     *
+        * @param switchId the openflow datapath id
+     * @return the list of openflow statistics
+     */
+    List<OFStatistics> getOFDescStatistics(Long switchId);
+
+    /**
+     * Returns the statistics for all the ports on the specified switch
+     *
+        * @param switchId the openflow datapath id
+     * @return the list of openflow statistics
+     */
+    List<OFStatistics> getOFPortStatistics(Long switchId);
+
+    /**
+     * Returns the statistics for the specified switch port
+     *
+        * @param switchId the openflow datapath id
+     * @param portId the openflow switch port id
+     * @return the list of openflow statistics
+     */
+    List<OFStatistics> getOFPortStatistics(Long switchId, short portId);
+
+    /**
+     * Returns the number of flows installed on the switch
+     *
+        * @param switchId the openflow datapath id
+     * @return the number of flows installed on the switch
+     */
+    int getFlowsNumber(long switchId);
+
+    /**
+     * Send a statistics request message to the specified switch and returns
+     * the switch response. It blocks the caller until the response has arrived
+     * from the switch or the request has timed out
+     *
+     * @param switchId the openflow datapath id of the target switch
+     * @param statType the openflow statistics type
+     * @param target the target object. For flow statistics it is the OFMatch.
+     *                                  For port statistics, it is the port id. If null the query
+     *                                  will be performed for all the targets for the specified
+     *                                  statistics type. 
+     *                                  
+     * @param timeout the timeout in milliseconds the system will wait for a response
+     *                   from the switch, before declaring failure 
+     * @return the list of openflow statistics
+     */
+    List<OFStatistics> queryStatistics(Long switchId,
+            OFStatisticsType statType, Object target, long timeout);
+
+    /**
+     * Returns the averaged transmit rate for the passed switch port
+     *
+     * @param switchId the openflow datapath id of the target switch
+     * @param portId the openflow switch port id
+     * @return the median transmit rate in bits per second
+     */
+    long getTransmitRate(Long switchId, Short port);
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IPluginReadServiceFilter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IPluginReadServiceFilter.java
new file mode 100644 (file)
index 0000000..a000024
--- /dev/null
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+import java.util.List;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.reader.NodeDescription;
+
+/**
+ * Interface to serve the hardware information requests coming from SAL
+ * It is implemented by the respective OF1.0 plugin component
+ *
+ */
+public interface IPluginReadServiceFilter {
+    /**
+     * Returns the hardware image for the specified flow
+     * on the specified network node for the passed container
+     *
+     * @param container
+     * @param node
+     * @param flow
+     * @param cached
+     * @return
+     */
+    public FlowOnNode readFlow(String container, Node node, Flow flow,
+            boolean cached);
+
+    /**
+     * Returns the hardware view of all the flow installed
+     * on the specified network node for the passed container
+     *
+     * @param container
+     * @param node
+     * @param cached
+     * @return
+     */
+    public List<FlowOnNode> readAllFlow(String container, Node node,
+            boolean cached);
+
+    /**
+     * Returns the description of the network node as provided by the node itself
+     *
+     * @param node
+     * @param cached
+     * @return
+     */
+    public NodeDescription readDescription(Node node, boolean cached);
+
+    /**
+     * Returns the hardware view of the specified network node connector
+     * for the given container
+     * @param node
+     * @return
+     */
+    public NodeConnectorStatistics readNodeConnector(String container,
+            NodeConnector nodeConnector, boolean cached);
+
+    /**
+     * Returns the hardware info for all the node connectors on the
+     * specified network node for the given container
+     *
+     * @param node
+     * @return
+     */
+    public List<NodeConnectorStatistics> readAllNodeConnector(String container,
+            Node node, boolean cached);
+
+    /**
+     * Returns the average transmit rate for the specified node conenctor on
+     * the given container. If the node connector does not belong to the passed
+     * container a zero value is returned
+     *
+     * @param container
+     * @param nodeConnector
+     * @return tx rate [bps]
+     */
+    public long getTransmitRate(String container, NodeConnector nodeConnector);
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IRefreshInternalProvider.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/IRefreshInternalProvider.java
new file mode 100644 (file)
index 0000000..fd762d4
--- /dev/null
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+/**
+ * @file       IRefreshInternalProvider.java
+ *
+ * @brief      Topology refresh notifications requested by application
+ *                     to be fetched from the plugin
+ *
+ * For example, an application that has been started late, will want to
+ * be up to date with the latest topology.  Hence, it requests for a
+ * topology refresh from the plugin.
+ */
+
+/**
+ * Topology refresh requested by the application from the plugin
+ *
+ */
+
+public interface IRefreshInternalProvider {
+
+    /**
+     * @param containerName
+     */
+    public void requestRefresh(String containerName);
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/ITopologyServiceShimListener.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/ITopologyServiceShimListener.java
new file mode 100644 (file)
index 0000000..23b40a3
--- /dev/null
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow;
+
+import java.util.Set;
+
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+
+/**
+ * Interface class that provides Edge updates to the topology listeners
+ *
+ *
+ */
+public interface ITopologyServiceShimListener {
+    /**
+     * Called to update on Edge in the topology graph
+     *
+     * @param edge                     {@link org.opendaylight.controller.sal.core.Edge} being updated
+     * @param type             {@link org.opendaylight.controller.sal.core.UpdateType}
+     * @param props            set of {@link org.opendaylight.controller.sal.core.Property} like
+     *                                                 {@link org.opendaylight.controller.sal.core.Bandwidth} and/or
+     *                                                 {@link org.opendaylight.controller.sal.core.Latency} etc.
+     */
+    public void edgeUpdate(Edge edge, UpdateType type, Set<Property> props);
+
+    /**
+     * Called when an Edge utilization is above the safe threshold configured
+     * on the controller
+     * @param {@link org.opendaylight.controller.sal.core.Edge}
+     */
+    public void edgeOverUtilized(Edge edge);
+
+    /**
+     * Called when the Edge utilization is back to normal, below the safety
+     * threshold level configured on the controller
+     *
+     * @param {@link org.opendaylight.controller.sal.core.Edge}
+     */
+    public void edgeUtilBackToNormal(Edge edge);
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/IController.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/IController.java
new file mode 100644 (file)
index 0000000..2cacafe
--- /dev/null
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core;
+
+import java.util.Map;
+
+import org.openflow.protocol.OFType;
+
+/**
+ * This interface defines an abstraction of the Open Flow Controller that allows applications to control and manage the Open Flow switches.
+ *
+ */
+public interface IController {
+
+    /**
+     * Allows application to start receiving OF messages received from switches.
+     * @param type the type of OF message that applications want to receive
+     * @param listener: Object that implements the IMessageListener
+     */
+    public void addMessageListener(OFType type, IMessageListener listener);
+
+    /**
+     * Allows application to stop receiving OF message received from switches.
+     * @param type The type of OF message that applications want to stop receiving
+     * @param listener The object that implements the IMessageListener
+     */
+    public void removeMessageListener(OFType type, IMessageListener listener);
+
+    /**
+     * Allows application to start receiving switch state change events.
+     * @param listener The object that implements the ISwitchStateListener
+     */
+    public void addSwitchStateListener(ISwitchStateListener listener);
+
+    /**
+     * Allows application to stop receiving switch state change events.
+     * @param listener The object that implements the ISwitchStateListener
+     */
+    public void removeSwitchStateListener(ISwitchStateListener listener);
+
+    /**
+     * Returns a map containing all the OF switches that are currently connected to the Controller.
+     * @return Map of ISwitch
+     */
+    public Map<Long, ISwitch> getSwitches();
+
+    /**
+     * Returns the ISwitch of the given switchId.
+     *
+     * @param switchId The switch ID
+     * @return ISwitch if present, null otherwise
+     */
+    public ISwitch getSwitch(Long switchId);
+    
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/IMessageListener.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/IMessageListener.java
new file mode 100644 (file)
index 0000000..aed19db
--- /dev/null
@@ -0,0 +1,26 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core;
+
+import org.openflow.protocol.OFMessage;
+
+/**
+ * Interface to be implemented by applications that want to receive OF messages.
+ *
+ */
+public interface IMessageListener {
+    /**
+     * This method is called by the Controller when a message is received from a switch.
+     * Application who is interested in receiving OF Messages needs to implement this method.
+     * @param sw The ISwitch which sent the OF message
+     * @param msg The OF message
+     */
+    public void receive(ISwitch sw, OFMessage msg);
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/ISwitch.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/ISwitch.java
new file mode 100644 (file)
index 0000000..0a4560e
--- /dev/null
@@ -0,0 +1,156 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFStatisticsRequest;
+
+/**
+ * This interface defines an abstraction of an Open Flow Switch.
+ *
+ */
+public interface ISwitch {
+       /**
+        * Gets a unique XID.
+        * @return XID
+        */
+       public int getNextXid();
+
+       /**
+        * Returns the Switch's ID.
+        * @return the Switch's ID
+        */
+       public Long getId();
+
+       /**
+        * Returns the Switch's table numbers supported by datapath 
+        * @return the tables
+        */
+       public Byte getTables();
+
+       /**
+        * Returns the Switch's bitmap of supported ofp_action_type
+        * @return the actions
+        */
+       public Integer getActions();
+
+       /**
+        * Returns the Switch's bitmap of supported ofp_capabilities
+        * @return the capabilities
+        */
+       public Integer getCapabilities();
+
+       /**
+        * Returns the Switch's buffering capacity in Number of Pkts
+        * @return the buffers
+        */
+       public Integer getBuffers();
+
+       /**
+        * Returns the Date when the switch was connected.
+        * @return Date The date when the switch was connected
+        */
+       public Date getConnectedDate();
+
+       /**
+        * Sends the message. A unique XID is generated automatically and inserted into the message.
+        * @param msg TheOF message to be sent
+        * @return The XID used
+        */
+       public Integer asyncSend(OFMessage msg);
+
+       /**
+        * Sends the message with the specified XID.
+        * @param msg The OF message to be Sent
+        * @param xid The XID to be used in the message
+        * @return The XID used
+        */
+       public Integer asyncSend(OFMessage msg, int xid);
+
+       /**
+        * Sends the OF message followed by a Barrier Request with a unique XID which is automatically generated,
+        * and waits for a result from the switch.
+        * @param msg The message to be sent
+        * @return An Object which has one of the followings instances/values:
+        * Boolean with value true to indicate the message has been successfully processed and acknowledged by the switch;
+        * Boolean with value false to indicate the message has failed to be processed by the switch within a period of time or
+        * OFError to indicate that the message has been denied by the switch which responded with OFError.
+        */
+       public Object syncSend(OFMessage msg);
+
+       /**
+        * Returns a map containing all OFPhysicalPorts of this switch.
+        * @return The Map of OFPhysicalPort
+        */
+       public Map<Short, OFPhysicalPort> getPhysicalPorts();
+
+       /**
+        * Returns a Set containing all port IDs of this switch.
+        * @return The Set of port ID
+        */
+       public Set<Short> getPorts();
+
+       /**
+        * Returns OFPhysicalPort of the specified portNumber of this switch.
+        * @param portNumber The port ID
+        * @return OFPhysicalPort for the specified PortNumber
+        */
+       public OFPhysicalPort getPhysicalPort(Short portNumber);
+
+       /**
+        * Returns the bandwidth of the specified portNumber of this switch.
+        * @param portNumber the port ID
+        * @return bandwidth
+        */
+       public Integer getPortBandwidth(Short portNumber);
+
+       /**
+        * Returns True if the port is enabled,
+        * @param portNumber 
+        * @return True if the port is enabled
+        */
+       public boolean isPortEnabled(short portNumber);
+
+       /**
+        * Returns True if the port is enabled.
+        * @param port
+        * @return True if the port is enabled
+        */
+       public boolean isPortEnabled(OFPhysicalPort port);
+
+       /**
+        * Returns a list containing all enabled ports of this switch.
+        * @return: List containing all enabled ports of this switch
+        */
+       public List<OFPhysicalPort> getEnabledPorts();
+
+       /**
+        * Sends OFStatisticsRequest  with a unique XID generated automatically and waits for a result from the switch.
+        * @param req the OF Statistic Request to be sent
+        * @return Object has one of the following instances/values::
+        * List<OFStatistics>, a list  of statistics records received from the switch as response from the request;
+        *      OFError if the switch failed handle the request or
+        * NULL if timeout has occurred while waiting for the response.
+        */
+       public Object getStatistics(OFStatisticsRequest req);
+
+       /**
+        * Returns true if the switch has reached the operational state (has sent FEATURE_REPLY to the controller).
+        * @return true if the switch is operational
+        */
+       public boolean isOperational();
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/ISwitchStateListener.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/ISwitchStateListener.java
new file mode 100644 (file)
index 0000000..608b6f5
--- /dev/null
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core;
+
+/**
+ * Interface to be implemented by applications that want to receive switch state event changes.
+ *
+ */
+public interface ISwitchStateListener {
+    /**
+     * This method is invoked by Controller when a switch has been connected to the Controller.
+     * Application who wants to receive this event needs to implement this method.
+     * @param sw The switch that has just connected.
+     */
+    public void switchAdded(ISwitch sw);
+
+    /**
+     * This method is invoked by Controller when a switch has been disconnected from the Controller.
+     * Application who wants to receive this event needs to implement this method.
+     * @param sw The switch that has just disconnected.
+     */
+    public void switchDeleted(ISwitch sw);
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/Controller.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/Controller.java
new file mode 100644 (file)
index 0000000..e628958
--- /dev/null
@@ -0,0 +1,355 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IMessageListener;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitchStateListener;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.util.HexString;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Controller implements IController, CommandProvider {
+    private static final Logger logger = LoggerFactory
+            .getLogger(Controller.class);
+    private ControllerIO controllerIO;
+    private Thread switchEventThread;
+    private ConcurrentHashMap<Long, ISwitch> switches;
+    private BlockingQueue<SwitchEvent> switchEvents;
+    // only 1 message listener per OFType
+    private ConcurrentMap<OFType, IMessageListener> messageListeners;
+    // only 1 switch state listener
+    private ISwitchStateListener switchStateListener;
+    private AtomicInteger switchInstanceNumber;
+
+    /*
+     * this thread monitors the switchEvents queue for new incoming events from switch
+     */
+    private class EventHandler implements Runnable {
+        @Override
+        public void run() {
+
+            while (true) {
+                try {
+                    SwitchEvent ev = switchEvents.take();
+                    SwitchEvent.SwitchEventType eType = ev.getEventType();
+                    ISwitch sw = ev.getSwitch();
+                    if (eType != SwitchEvent.SwitchEventType.SWITCH_MESSAGE) {
+                        //logger.debug("Received " + ev.toString() + " from " + sw.toString());
+                    }
+                    switch (eType) {
+                    case SWITCH_ADD:
+                        Long sid = sw.getId();
+                        ISwitch existingSwitch = switches.get(sid);
+                        if (existingSwitch != null) {
+                            logger.info(" Replacing existing "
+                                    + existingSwitch.toString() + " with New "
+                                    + sw.toString());
+                            disconnectSwitch(existingSwitch);
+                        }
+                        switches.put(sid, sw);
+                        notifySwitchAdded(sw);
+                        break;
+                    case SWITCH_DELETE:
+                        disconnectSwitch(sw);
+                        break;
+                    case SWITCH_ERROR:
+                        disconnectSwitch(sw);
+                        break;
+                    case SWITCH_MESSAGE:
+                        OFMessage msg = ev.getMsg();
+                        if (msg != null) {
+                            IMessageListener listener = messageListeners
+                                    .get(msg.getType());
+                            if (listener != null) {
+                                listener.receive(sw, msg);
+                            }
+                        }
+                        break;
+                    default:
+                        logger.error("unknow switch event " + eType.ordinal());
+                    }
+                } catch (InterruptedException e) {
+                    switchEvents.clear();
+                    return;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    public void init() {
+        logger.debug("OpenFlowCore init");
+        this.switches = new ConcurrentHashMap<Long, ISwitch>();
+        this.switchEvents = new LinkedBlockingQueue<SwitchEvent>();
+        this.messageListeners = new ConcurrentHashMap<OFType, IMessageListener>();
+        this.switchStateListener = null;
+        this.switchInstanceNumber = new AtomicInteger(0);
+        registerWithOSGIConsole();
+
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    public void start() {
+        logger.debug("OpenFlowCore start() is called");
+        /*
+         * start a thread to handle event coming from the switch
+         */
+        switchEventThread = new Thread(new EventHandler(), "SwitchEvent Thread");
+        switchEventThread.start();
+
+        // spawn a thread to start to listen on the open flow port
+        controllerIO = new ControllerIO(this);
+        try {
+            controllerIO.start();
+        } catch (IOException ex) {
+            logger.error("Caught exception: " + ex + " during start");
+        }
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    public void stop() {
+
+        for (Iterator<Entry<Long, ISwitch>> it = switches.entrySet().iterator(); it
+                .hasNext();) {
+            Entry<Long, ISwitch> entry = it.next();
+            ((SwitchHandler) entry.getValue()).stop();
+            it.remove();
+        }
+        switchEventThread.interrupt();
+        controllerIO.shutDown();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    public void destroy() {
+    }
+
+    @Override
+    public void addMessageListener(OFType type, IMessageListener listener) {
+        IMessageListener currentListener = this.messageListeners.get(type);
+        if (currentListener != null) {
+            logger.warn(type.toString() + " already listened by "
+                    + currentListener.toString());
+        }
+        this.messageListeners.put(type, listener);
+        logger.debug(type.toString() + " is now listened by "
+                + listener.toString());
+    }
+
+    @Override
+    public void removeMessageListener(OFType type, IMessageListener listener) {
+        IMessageListener currentListener = this.messageListeners.get(type);
+        if ((currentListener != null) && (currentListener == listener)) {
+            this.messageListeners.remove(type);
+        }
+    }
+
+    @Override
+    public void addSwitchStateListener(ISwitchStateListener listener) {
+        if (this.switchStateListener != null) {
+            logger.warn(this.switchStateListener.toString()
+                    + "already listened to switch events");
+        }
+        this.switchStateListener = listener;
+        logger.debug(listener.toString() + " now listens to switch events");
+    }
+
+    @Override
+    public void removeSwitchStateListener(ISwitchStateListener listener) {
+        if ((this.switchStateListener != null)
+                && (this.switchStateListener == listener)) {
+            this.switchStateListener = null;
+        }
+    }
+
+    public void handleNewConnection(Selector selector,
+            SelectionKey serverSelectionKey) {
+        ServerSocketChannel ssc = (ServerSocketChannel) serverSelectionKey
+                .channel();
+        SocketChannel sc = null;
+        try {
+            sc = ssc.accept();
+            // create new switch
+            int i = this.switchInstanceNumber.addAndGet(1);
+            String instanceName = "SwitchHandler-" + i;
+            SwitchHandler switchHandler = new SwitchHandler(this, sc,
+                    instanceName);
+            switchHandler.start();
+            logger.info(instanceName + " connected: " + sc.toString());
+        } catch (IOException e) {
+            logger
+                    .error("Caught I/O Exception when trying to accept a new connection");
+            return;
+        }
+    }
+
+    private void disconnectSwitch(ISwitch sw) {
+        if (((SwitchHandler) sw).isOperational()) {
+            Long sid = sw.getId();
+            if (this.switches.remove(sid, sw)) {
+                logger.warn(sw.toString() + " is disconnected");
+                notifySwitchDeleted(sw);
+            } else {
+                //logger.warn(sw.toString() + " has been replaced by " +
+                //     this.switches.get(sid));
+            }
+        }
+        ((SwitchHandler) sw).stop();
+    }
+
+    private void notifySwitchAdded(ISwitch sw) {
+        if (switchStateListener != null) {
+            switchStateListener.switchAdded(sw);
+        }
+    }
+
+    private void notifySwitchDeleted(ISwitch sw) {
+        if (switchStateListener != null) {
+            switchStateListener.switchDeleted(sw);
+        }
+    }
+
+    private synchronized void addSwitchEvent(SwitchEvent event) {
+        try {
+            this.switchEvents.put(event);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            logger.error("Interrupt Exception " + e.toString());
+        }
+    }
+
+    public void takeSwtichEventAdd(ISwitch sw) {
+        SwitchEvent ev = new SwitchEvent(
+                SwitchEvent.SwitchEventType.SWITCH_ADD, sw, null);
+        addSwitchEvent(ev);
+    }
+
+    public void takeSwitchEventDelete(ISwitch sw) {
+        SwitchEvent ev = new SwitchEvent(
+                SwitchEvent.SwitchEventType.SWITCH_DELETE, sw, null);
+        addSwitchEvent(ev);
+    }
+
+    public void takeSwitchEventError(ISwitch sw) {
+        SwitchEvent ev = new SwitchEvent(
+                SwitchEvent.SwitchEventType.SWITCH_ERROR, sw, null);
+        addSwitchEvent(ev);
+    }
+
+    public void takeSwitchEventMsg(ISwitch sw, OFMessage msg) {
+        if (messageListeners.get(msg.getType()) != null) {
+            SwitchEvent ev = new SwitchEvent(
+                    SwitchEvent.SwitchEventType.SWITCH_MESSAGE, sw, msg);
+            addSwitchEvent(ev);
+        }
+    }
+
+    @Override
+    public Map<Long, ISwitch> getSwitches() {
+        return this.switches;
+    }
+
+    @Override
+    public ISwitch getSwitch(Long switchId) {
+        return this.switches.get(switchId);
+    }
+
+    public void _controllerShowSwitches(CommandInterpreter ci) {
+        Set<Long> sids = switches.keySet();
+        StringBuffer s = new StringBuffer();
+        int size = sids.size();
+        if (size == 0) {
+            ci.print("switches: empty");
+            return;
+        }
+        Iterator<Long> iter = sids.iterator();
+        s.append("Total: " + size + " switches\n");
+        while (iter.hasNext()) {
+            Long sid = iter.next();
+            Date date = switches.get(sid).getConnectedDate();
+            String switchInstanceName = ((SwitchHandler) switches.get(sid)).getInstanceName();
+            s.append(switchInstanceName + "/" + HexString.toHexString(sid)
+                    + " connected since " + date.toString() + "\n");
+        }
+        ci.print(s.toString());
+        return;
+    }
+
+    public void _controllerReset(CommandInterpreter ci) {
+        ci.print("...Disconnecting the communication to all switches...\n");
+        stop();
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException ie) {
+        } finally {
+            ci.print("...start to accept connections from switches...\n");
+            start();
+        }
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("--Open Flow Controller --\n");
+        help.append("\tcontrollerShowSwitches\n");
+        help.append("\tcontrollerReset\n");
+        return help.toString();
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/ControllerIO.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/ControllerIO.java
new file mode 100644 (file)
index 0000000..e1bc2d0
--- /dev/null
@@ -0,0 +1,99 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.Iterator;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControllerIO {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ControllerIO.class);
+    private static Short defaultOpenFlowPort = 6633;
+    private Short openFlowPort;
+    private SelectionKey serverSelectionKey;
+    private IController listener;
+    private ServerSocketChannel serverSocket;
+    private Selector selector;
+    private boolean running;
+    private Thread controllerIOThread;
+
+    public ControllerIO(IController l) {
+        this.listener = l;
+        this.openFlowPort = defaultOpenFlowPort;
+        String portString = System.getProperty("port");
+        if (portString != null) {
+            try {
+                openFlowPort = Short.decode(portString).shortValue();
+            } catch (NumberFormatException e) {
+                logger.warn("Invalid port:" + portString + ", use default("
+                        + openFlowPort + ")");
+            }
+        }
+    }
+
+    public void start() throws IOException {
+        this.running = true;
+        // obtain a selector
+        this.selector = SelectorProvider.provider().openSelector();
+        // create the listening socket
+        this.serverSocket = ServerSocketChannel.open();
+        this.serverSocket.configureBlocking(false);
+        this.serverSocket.socket().bind(
+                new java.net.InetSocketAddress(openFlowPort));
+        this.serverSocket.socket().setReuseAddress(true);
+        // register this socket for accepting incoming connections
+        this.serverSelectionKey = this.serverSocket.register(selector,
+                SelectionKey.OP_ACCEPT);
+        controllerIOThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (running) {
+                    try {
+                        // wait for an incoming connection
+                        selector.select(0);
+                        Iterator<SelectionKey> selectedKeys = selector
+                                .selectedKeys().iterator();
+                        while (selectedKeys.hasNext()) {
+                            SelectionKey skey = selectedKeys.next();
+                            selectedKeys.remove();
+                            if (skey.isValid() && skey.isAcceptable()) {
+                                 ((Controller) listener).handleNewConnection(selector,
+                                        serverSelectionKey);
+                            }
+                        }
+                    } catch (IOException e) {
+                        logger.error("Caught I/O Exception: " + e.toString());
+                        return;
+                    }
+                }
+            }
+        }, "ControllerI/O Thread");
+        controllerIOThread.start();
+        logger.info("Controller is now listening on port " + openFlowPort);
+    }
+
+    public void shutDown() {
+        this.running = false;
+        this.selector.wakeup();
+        try {
+            this.serverSocket.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/StatisticsCollector.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/StatisticsCollector.java
new file mode 100644 (file)
index 0000000..98fb803
--- /dev/null
@@ -0,0 +1,81 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFStatisticsReply;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFStatistics;
+
+public class StatisticsCollector implements Callable<Object> {
+
+    private ISwitch sw;
+    private Integer xid;
+    private OFStatisticsRequest request;
+    private CountDownLatch latch;
+    private Object result;
+    private List<OFStatistics> stats;
+
+    public StatisticsCollector(ISwitch sw, int xid, OFStatisticsRequest request) {
+        this.sw = sw;
+        this.xid = xid;
+        this.request = request;
+        latch = new CountDownLatch(1);
+        result = new Object();
+        stats = new CopyOnWriteArrayList<OFStatistics>();
+    }
+
+    /*
+     * accumulate the stats records in result
+     * Returns: true: if this is the last record
+     *                 false: more to come
+     */
+    public boolean collect(OFStatisticsReply reply) {
+        synchronized (result) {
+            stats.addAll(reply.getStatistics());
+            if ((reply.getFlags() & 0x01) == 0) {
+                // all stats are collected, done
+                result = stats;
+                return true;
+            } else {
+                // still waiting for more to come
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public Object call() throws Exception {
+        sw.asyncSend(request, xid);
+        // free up the request as it is no longer needed
+        request = null;
+        // wait until all stats replies are received or timeout
+        latch.await();
+        return result;
+    }
+
+    public Integer getXid() {
+        return this.xid;
+    }
+
+    public void wakeup() {
+        this.latch.countDown();
+    }
+
+    public void wakeup(OFError errorMsg) {
+
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SwitchEvent.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SwitchEvent.java
new file mode 100644 (file)
index 0000000..87e30d7
--- /dev/null
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFMessage;
+
+public class SwitchEvent {
+
+    public static enum SwitchEventType {
+        SWITCH_ADD, SWITCH_DELETE, SWITCH_ERROR, SWITCH_MESSAGE,
+    }
+
+    private SwitchEventType eventType;
+    private ISwitch sw;
+    private OFMessage msg;
+
+    public SwitchEvent(SwitchEventType type, ISwitch sw, OFMessage msg) {
+        this.eventType = type;
+        this.sw = sw;
+        this.msg = msg;
+    }
+
+    public SwitchEventType getEventType() {
+        return this.eventType;
+    }
+
+    public ISwitch getSwitch() {
+        return this.sw;
+    }
+
+    public OFMessage getMsg() {
+        return this.msg;
+    }
+
+    @Override
+    public String toString() {
+        String s;
+        switch (this.eventType) {
+        case SWITCH_ADD:
+            s = "SWITCH_ADD";
+            break;
+        case SWITCH_DELETE:
+            s = "SWITCH_DELETE";
+            break;
+        case SWITCH_ERROR:
+            s = "SWITCH_ERROR";
+            break;
+        case SWITCH_MESSAGE:
+            s = "SWITCH_MESSAGE";
+            break;
+        default:
+            s = "?" + this.eventType.ordinal() + "?";
+        }
+        return "Switch Event: " + s;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SwitchHandler.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SwitchHandler.java
new file mode 100644 (file)
index 0000000..c4a85c0
--- /dev/null
@@ -0,0 +1,709 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFBarrierReply;
+import org.openflow.protocol.OFEchoReply;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFGetConfigReply;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPhysicalPort.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortFeatures;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFSetConfig;
+import org.openflow.protocol.OFStatisticsReply;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SwitchHandler implements ISwitch {
+    private static final Logger logger = LoggerFactory
+            .getLogger(SwitchHandler.class);
+    private static final int SWITCH_LIVENESS_TIMER = 5000;
+    private static final int SWITCH_LIVENESS_TIMEOUT = 2 * SWITCH_LIVENESS_TIMER + 500;
+    private static final int SYNCHRONOUS_FLOW_TIMEOUT = 2000;
+    private static final int STATS_COLLECTION_TIMEOUT = 2000;
+    private static final int bufferSize = 1024 * 1024;
+
+    private String instanceName;
+    private ISwitch thisISwitch;
+    private IController core;
+    private Long sid;
+    private Integer buffers;
+    private Integer capabilities;
+    private Byte tables;
+    private Integer actions;
+    private Selector selector;
+    private SelectionKey clientSelectionKey;
+    private SocketChannel socket;
+    private ByteBuffer inBuffer;
+    private ByteBuffer outBuffer;
+    private BasicFactory factory;
+    private AtomicInteger xid;
+    private SwitchState state;
+    private Timer periodicTimer;
+    private Map<Short, OFPhysicalPort> physicalPorts;
+    private Map<Short, Integer> portBandwidth;
+    private Date connectedDate;
+    private Long lastMsgReceivedTimeStamp;
+    private Boolean probeSent;
+    private ExecutorService executor;
+    private ConcurrentHashMap<Integer, Callable<Object>> messageWaitingDone;
+    private boolean running;
+    private Thread switchHandlerThread;
+
+    private enum SwitchState {
+        NON_OPERATIONAL(0), WAIT_FEATURES_REPLY(1), WAIT_CONFIG_REPLY(2), OPERATIONAL(
+                3);
+
+        private int value;
+
+        private SwitchState(int value) {
+            this.value = value;
+        }
+
+        @SuppressWarnings("unused")
+        public int value() {
+            return this.value;
+        }
+    }
+
+    public SwitchHandler(Controller core, SocketChannel sc, String name) {
+        this.instanceName = name;
+        this.thisISwitch = this;
+        this.sid = (long) 0;
+        this.buffers = (int)0;
+        this.capabilities = (int)0;
+        this.tables = (byte)0;
+        this.actions = (int)0;
+        this.core = core;
+        this.socket = sc;
+        this.factory = new BasicFactory();
+        this.connectedDate = new Date();
+        this.lastMsgReceivedTimeStamp = connectedDate.getTime();
+        this.physicalPorts = new HashMap<Short, OFPhysicalPort>();
+        this.portBandwidth = new HashMap<Short, Integer>();
+        this.state = SwitchState.NON_OPERATIONAL;
+        this.probeSent = false;
+        this.xid = new AtomicInteger(this.socket.hashCode());
+        this.periodicTimer = null;
+        this.executor = Executors.newFixedThreadPool(4);
+        this.messageWaitingDone = new ConcurrentHashMap<Integer, Callable<Object>>();
+        this.inBuffer = ByteBuffer.allocateDirect(bufferSize);
+        this.outBuffer = ByteBuffer.allocateDirect(bufferSize);
+    }
+
+    public void start() {
+        try {
+            this.selector = SelectorProvider.provider().openSelector();
+            this.socket.configureBlocking(false);
+            this.socket.socket().setTcpNoDelay(true);
+            this.clientSelectionKey = this.socket.register(this.selector,
+                    SelectionKey.OP_READ);
+            startHandlerThread();
+        } catch (Exception e) {
+            reportError(e);
+            return;
+        }
+    }
+
+    private void startHandlerThread() {
+        OFMessage msg = factory.getMessage(OFType.HELLO);
+        asyncSend(msg);
+        switchHandlerThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                running = true;
+                while (running) {
+                    try {
+                        // wait for an incoming connection
+                        selector.select(0);
+                        Iterator<SelectionKey> selectedKeys = selector
+                                .selectedKeys().iterator();
+                        while (selectedKeys.hasNext()) {
+                            SelectionKey skey = selectedKeys.next();
+                            selectedKeys.remove();
+                            if (skey.isValid() && skey.isWritable()) {
+                                resumeSend();
+                            }
+                            if (skey.isValid() && skey.isReadable()) {
+                                handleMessages();
+                            }
+                        }
+                    } catch (IOException e) {
+                        logger.error("Caught I/O Exception: " + e.toString());
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }, instanceName);
+        switchHandlerThread.start();
+    }
+
+    public void stop() {
+        try {
+            running = false;
+            selector.wakeup();
+            cancelSwitchTimer();
+            this.clientSelectionKey.cancel();
+            this.socket.close();
+            executor.shutdown();
+        } catch (IOException e) {
+            logger.error("Caught IOException in stop()");
+        }
+    }
+
+    @Override
+    public int getNextXid() {
+        return this.xid.incrementAndGet();
+    }
+
+    @Override
+    public Integer asyncSend(OFMessage msg) {
+        return asyncSend(msg, getNextXid());
+    }
+
+    @Override
+    public Integer asyncSend(OFMessage msg, int xid) {
+        synchronized (outBuffer) {
+            /*
+            if ((msg.getType() != OFType.ECHO_REQUEST) &&
+                       (msg.getType() != OFType.ECHO_REPLY)) {
+               logger.debug("sending " + msg.getType().toString() + " to " + toString());
+            }
+             */
+            msg.setXid(xid);
+            int msgLen = msg.getLengthU();
+            if (outBuffer.remaining() < msgLen) {
+                // increase the buffer size so that it can contain this message
+                ByteBuffer newBuffer = ByteBuffer.allocateDirect(outBuffer
+                        .capacity()
+                        + msgLen);
+                outBuffer.flip();
+                newBuffer.put(outBuffer);
+                outBuffer = newBuffer;
+            }
+            msg.writeTo(outBuffer);
+            outBuffer.flip();
+            try {
+                socket.write(outBuffer);
+                outBuffer.compact();
+                if (outBuffer.position() > 0) {
+                    this.clientSelectionKey = this.socket.register(
+                            this.selector, SelectionKey.OP_WRITE, this);
+                }
+                logger.trace("Message sent: " + msg.toString());
+            } catch (IOException e) {
+                reportError(e);
+            }
+        }
+        return xid;
+    }
+
+    public void resumeSend() {
+        synchronized (outBuffer) {
+            try {
+                outBuffer.flip();
+                socket.write(outBuffer);
+                outBuffer.compact();
+                if (outBuffer.position() > 0) {
+                    this.clientSelectionKey = this.socket.register(
+                            this.selector, SelectionKey.OP_WRITE, this);
+                } else {
+                    this.clientSelectionKey = this.socket.register(
+                            this.selector, SelectionKey.OP_READ, this);
+                }
+            } catch (IOException e) {
+                reportError(e);
+            }
+        }
+    }
+
+    public void handleMessages() {
+        List<OFMessage> msgs = readMessages();
+        if (msgs == null) {
+            logger.debug(toString() + " is down");
+            // the connection is down, inform core
+            reportSwitchStateChange(false);
+            return;
+        }
+        for (OFMessage msg : msgs) {
+            logger.trace("Message received: " + msg.toString());
+            /*
+            if  ((msg.getType() != OFType.ECHO_REQUEST) &&
+                       (msg.getType() != OFType.ECHO_REPLY)) {
+               logger.debug(msg.getType().toString() + " received from sw " + toString());
+            }
+             */
+            this.lastMsgReceivedTimeStamp = System.currentTimeMillis();
+            OFType type = msg.getType();
+            switch (type) {
+            case HELLO:
+                // send feature request
+                OFMessage featureRequest = factory
+                        .getMessage(OFType.FEATURES_REQUEST);
+                asyncSend(featureRequest);
+                // delete all pre-existing flows
+                OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL);
+                OFFlowMod flowMod = (OFFlowMod) factory
+                        .getMessage(OFType.FLOW_MOD);
+                flowMod.setMatch(match).setCommand(OFFlowMod.OFPFC_DELETE)
+                        .setOutPort(OFPort.OFPP_NONE).setLength(
+                                (short) OFFlowMod.MINIMUM_LENGTH);
+                asyncSend(flowMod);
+                this.state = SwitchState.WAIT_FEATURES_REPLY;
+                startSwitchTimer();
+                break;
+            case ECHO_REQUEST:
+                OFEchoReply echoReply = (OFEchoReply) factory
+                        .getMessage(OFType.ECHO_REPLY);
+                asyncSend(echoReply);
+                break;
+            case ECHO_REPLY:
+                this.probeSent = false;
+                break;
+            case FEATURES_REPLY:
+                processFeaturesReply((OFFeaturesReply) msg);
+                break;
+            case GET_CONFIG_REPLY:
+                // make sure that the switch can send the whole packet to the controller
+                if (((OFGetConfigReply) msg).getMissSendLength() == (short) 0xffff) {
+                    this.state = SwitchState.OPERATIONAL;
+                }
+                break;
+            case BARRIER_REPLY:
+                processBarrierReply((OFBarrierReply) msg);
+                break;
+            case ERROR:
+                processErrorReply((OFError) msg);
+                break;
+            case PORT_STATUS:
+                processPortStatusMsg((OFPortStatus) msg);
+                break;
+            case STATS_REPLY:
+                processStatsReply((OFStatisticsReply) msg);
+                break;
+            case PACKET_IN:
+                break;
+            default:
+                break;
+            } // end of switch
+            if (isOperational()) {
+                ((Controller) core).takeSwitchEventMsg(thisISwitch, msg);
+            }
+        } // end of for
+    }
+
+    private void processPortStatusMsg(OFPortStatus msg) {
+        //short portNumber = msg.getDesc().getPortNumber();
+        OFPhysicalPort port = msg.getDesc();
+        if (msg.getReason() == (byte) OFPortReason.OFPPR_MODIFY.ordinal()) {
+            updatePhysicalPort(port);
+            //logger.debug("Port " + portNumber + " on " + toString() + " modified");
+        } else if (msg.getReason() == (byte) OFPortReason.OFPPR_ADD.ordinal()) {
+            updatePhysicalPort(port);
+            //logger.debug("Port " + portNumber + " on " + toString() + " added");
+        } else if (msg.getReason() == (byte) OFPortReason.OFPPR_DELETE
+                .ordinal()) {
+            deletePhysicalPort(port);
+            //logger.debug("Port " + portNumber + " on " + toString() + " deleted");
+        }
+
+    }
+
+    private List<OFMessage> readMessages() {
+        List<OFMessage> msgs = null;
+        int bytesRead;
+        try {
+            bytesRead = socket.read(inBuffer);
+        } catch (IOException e) {
+            reportError(e);
+            return null;
+        }
+        if (bytesRead == -1) {
+            return null;
+        }
+        inBuffer.flip();
+        msgs = factory.parseMessages(inBuffer);
+        if (inBuffer.hasRemaining()) {
+            inBuffer.compact();
+        } else {
+            inBuffer.clear();
+        }
+        return msgs;
+    }
+
+    private void startSwitchTimer() {
+        this.periodicTimer = new Timer();
+        this.periodicTimer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                try {
+                    Long now = System.currentTimeMillis();
+                    if ((now - lastMsgReceivedTimeStamp) > SWITCH_LIVENESS_TIMEOUT) {
+                        if (probeSent) {
+                            // switch failed to respond to our probe, consider it down
+                            logger.warn(toString()
+                                    + " is idle for too long, disconnect");
+                            reportSwitchStateChange(false);
+                        } else {
+                            // send a probe to see if the switch is still alive
+                            //logger.debug("Send idle probe (Echo Request) to " + switchName());
+                            probeSent = true;
+                            OFMessage echo = factory
+                                    .getMessage(OFType.ECHO_REQUEST);
+                            asyncSend(echo);
+                        }
+                    } else {
+                        if (state == SwitchState.WAIT_FEATURES_REPLY) {
+                            // send another features request
+                            OFMessage request = factory
+                                    .getMessage(OFType.FEATURES_REQUEST);
+                            asyncSend(request);
+                        } else {
+                            if (state == SwitchState.WAIT_CONFIG_REPLY) {
+                                //  send another config request
+                                OFSetConfig config = (OFSetConfig) factory
+                                        .getMessage(OFType.SET_CONFIG);
+                                config.setMissSendLength((short) 0xffff)
+                                        .setLengthU(OFSetConfig.MINIMUM_LENGTH);
+                                asyncSend(config);
+                                OFMessage getConfig = factory
+                                        .getMessage(OFType.GET_CONFIG_REQUEST);
+                                asyncSend(getConfig);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    reportError(e);
+                }
+            }
+        }, SWITCH_LIVENESS_TIMER, SWITCH_LIVENESS_TIMER);
+    }
+
+    private void cancelSwitchTimer() {
+        if (this.periodicTimer != null) {
+            this.periodicTimer.cancel();
+        }
+    }
+
+    private void reportError(Exception e) {
+        //logger.error(toString() + " caught Error " + e.toString());
+        // notify core of this error event
+        ((Controller) core).takeSwitchEventError(this);
+    }
+
+    private void reportSwitchStateChange(boolean added) {
+        if (added) {
+            ((Controller) core).takeSwtichEventAdd(this);
+        } else {
+            ((Controller) core).takeSwitchEventDelete(this);
+        }
+    }
+
+    @Override
+    public Long getId() {
+        return this.sid;
+    }
+
+    private void processFeaturesReply(OFFeaturesReply reply) {
+        if (this.state == SwitchState.WAIT_FEATURES_REPLY) {
+            this.sid = reply.getDatapathId();
+            this.buffers = reply.getBuffers();
+            this.capabilities = reply.getCapabilities();
+            this.tables = reply.getTables();
+            this.actions = reply.getActions();
+            // notify core of this error event
+            for (OFPhysicalPort port : reply.getPorts()) {
+                updatePhysicalPort(port);
+            }
+            // config the switch to send full data packet
+            OFSetConfig config = (OFSetConfig) factory
+                    .getMessage(OFType.SET_CONFIG);
+            config.setMissSendLength((short) 0xffff).setLengthU(
+                    OFSetConfig.MINIMUM_LENGTH);
+            asyncSend(config);
+            // send config request to make sure the switch can handle the set config
+            OFMessage getConfig = factory.getMessage(OFType.GET_CONFIG_REQUEST);
+            asyncSend(getConfig);
+            this.state = SwitchState.WAIT_CONFIG_REPLY;
+            // inform core that a new switch is now operational
+            reportSwitchStateChange(true);
+        }
+    }
+
+    private void updatePhysicalPort(OFPhysicalPort port) {
+        Short portNumber = port.getPortNumber();
+        physicalPorts.put(portNumber, port);
+        portBandwidth
+                .put(
+                        portNumber,
+                        port.getCurrentFeatures()
+                                & (OFPortFeatures.OFPPF_10MB_FD.getValue()
+                                        | OFPortFeatures.OFPPF_10MB_HD
+                                                .getValue()
+                                        | OFPortFeatures.OFPPF_100MB_FD
+                                                .getValue()
+                                        | OFPortFeatures.OFPPF_100MB_HD
+                                                .getValue()
+                                        | OFPortFeatures.OFPPF_1GB_FD
+                                                .getValue()
+                                        | OFPortFeatures.OFPPF_1GB_HD
+                                                .getValue() | OFPortFeatures.OFPPF_10GB_FD
+                                        .getValue()));
+    }
+
+    private void deletePhysicalPort(OFPhysicalPort port) {
+        Short portNumber = port.getPortNumber();
+        physicalPorts.remove(portNumber);
+        portBandwidth.remove(portNumber);
+    }
+
+    @Override
+    public boolean isOperational() {
+        return ((this.state == SwitchState.WAIT_CONFIG_REPLY) || (this.state == SwitchState.OPERATIONAL));
+    }
+
+    @Override
+    public String toString() {
+        return ("["
+                + this.socket.toString()
+                + " SWID "
+                + (isOperational() ? HexString.toHexString(this.sid)
+                        : "unkbown") + "]");
+    }
+
+    @Override
+    public Date getConnectedDate() {
+        return this.connectedDate;
+    }
+
+    public String getInstanceName() {
+        return instanceName;
+    }
+
+    @Override
+    public Object getStatistics(OFStatisticsRequest req) {
+        int xid = getNextXid();
+        StatisticsCollector worker = new StatisticsCollector(this, xid, req);
+        messageWaitingDone.put(xid, worker);
+        Future<Object> submit = executor.submit(worker);
+        Object result = null;
+        try {
+            result = submit
+                    .get(STATS_COLLECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+            return result;
+        } catch (Exception e) {
+            logger.warn("Timeout while waiting for " + req.getType()
+                    + " replies");
+            result = null; // to indicate timeout has occurred
+            return result;
+        }
+    }
+
+    @Override
+    public Object syncSend(OFMessage msg) {
+        Integer xid = getNextXid();
+        SynchronousMessage worker = new SynchronousMessage(this, xid, msg);
+        messageWaitingDone.put(xid, worker);
+        Object result = null;
+        Boolean status = false;
+        Future<Object> submit = executor.submit(worker);
+        try {
+            result = submit
+                    .get(SYNCHRONOUS_FLOW_TIMEOUT, TimeUnit.MILLISECONDS);
+            messageWaitingDone.remove(xid);
+            if (result == null) {
+                // if result  is null, then it means the switch can handle this message successfully
+                // convert the result into a Boolean with value true
+                status = true;
+                //logger.debug("Successfully send " + msg.getType().toString());
+                result = status;
+            } else {
+                // if result  is not null, this means the switch can't handle this message
+                // the result if OFError already
+                logger.debug("Send " + msg.getType().toString()
+                        + " failed --> " + ((OFError) result).toString());
+            }
+            return result;
+        } catch (Exception e) {
+            logger.warn("Timeout while waiting for " + msg.getType().toString()
+                    + " reply");
+            // convert the result into a Boolean with value false
+            status = false;
+            result = status;
+            return result;
+        }
+    }
+
+    /*
+     * Either a BarrierReply or a OFError is received. If this is a reply for an outstanding sync message,
+     * wake up associated task so that it can continue
+     */
+    private void processBarrierReply(OFBarrierReply msg) {
+        Integer xid = msg.getXid();
+        SynchronousMessage worker = (SynchronousMessage) messageWaitingDone
+                .remove(xid);
+        if (worker == null) {
+            return;
+        }
+        worker.wakeup();
+    }
+
+    private void processErrorReply(OFError errorMsg) {
+        OFMessage offendingMsg = errorMsg.getOffendingMsg();
+        Integer xid;
+        if (offendingMsg != null) {
+            xid = offendingMsg.getXid();
+        } else {
+            xid = errorMsg.getXid();
+        }
+        /*
+         * the error can be a reply to a synchronous message or to a statistic request message
+         */
+        Callable<?> worker = messageWaitingDone.remove(xid);
+        if (worker == null) {
+            return;
+        }
+        if (worker instanceof SynchronousMessage) {
+            ((SynchronousMessage) worker).wakeup(errorMsg);
+        } else {
+            ((StatisticsCollector) worker).wakeup(errorMsg);
+        }
+    }
+
+    private void processStatsReply(OFStatisticsReply reply) {
+        Integer xid = reply.getXid();
+        StatisticsCollector worker = (StatisticsCollector) messageWaitingDone
+                .get(xid);
+        if (worker == null) {
+            return;
+        }
+        if (worker.collect(reply)) {
+            // if all the stats records are received (collect() returns true)
+            // then we are done.
+            messageWaitingDone.remove(xid);
+            worker.wakeup();
+        }
+    }
+
+    @Override
+    public Map<Short, OFPhysicalPort> getPhysicalPorts() {
+        return this.physicalPorts;
+    }
+
+    @Override
+    public OFPhysicalPort getPhysicalPort(Short portNumber) {
+        return this.physicalPorts.get(portNumber);
+    }
+
+    @Override
+    public Integer getPortBandwidth(Short portNumber) {
+        return this.portBandwidth.get(portNumber);
+    }
+
+    @Override
+    public Set<Short> getPorts() {
+        return this.physicalPorts.keySet();
+    }
+
+    @Override
+    public Byte getTables() {
+        return this.tables;
+    }
+    
+    @Override
+    public Integer getActions() {
+        return this.actions;
+    }
+    
+    @Override
+    public Integer getCapabilities() {
+        return this.capabilities;
+    }
+
+    @Override
+    public Integer getBuffers() {
+        return this.buffers;
+    }
+
+    @Override
+    public boolean isPortEnabled(short portNumber) {
+        return isPortEnabled(physicalPorts.get(portNumber));
+    }
+
+    @Override
+    public boolean isPortEnabled(OFPhysicalPort port) {
+        if (port == null) {
+            return false;
+        }
+        int portConfig = port.getConfig();
+        int portState = port.getState();
+        if ((portConfig & OFPortConfig.OFPPC_PORT_DOWN.getValue()) > 0) {
+            return false;
+        }
+        if ((portState & OFPortState.OFPPS_LINK_DOWN.getValue()) > 0) {
+            return false;
+        }
+        if ((portState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK
+                .getValue()) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public List<OFPhysicalPort> getEnabledPorts() {
+        List<OFPhysicalPort> result = new ArrayList<OFPhysicalPort>();
+        synchronized (this.physicalPorts) {
+            for (OFPhysicalPort port : physicalPorts.values()) {
+                if (isPortEnabled(port)) {
+                    result.add(port);
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SynchronousMessage.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/core/internal/SynchronousMessage.java
new file mode 100644 (file)
index 0000000..5e613e8
--- /dev/null
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFBarrierRequest;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFMessage;
+
+/**
+ * Implements the synchronous message send to a switch
+ * It sends the requested message to the switch followed by a barrier request message
+ * It returns the result once it gets the reply from the switch or after a timeout
+ * If the protocol does not dictate the switch to reply the processing status for a particular message
+ * the barrier request forces the switch to reply saying whether or not the message processing was
+ * successful for messages sent to the switch up to this point
+ *
+ *
+ *
+ */
+public class SynchronousMessage implements Callable<Object> {
+    private ISwitch sw;
+    private Integer xid;
+    private OFMessage syncMsg;
+    protected CountDownLatch latch;
+    private Object result;
+
+    public SynchronousMessage(ISwitch sw, Integer xid, OFMessage msg) {
+        this.sw = sw;
+        this.xid = xid;
+        syncMsg = msg;
+        latch = new CountDownLatch(1);
+        result = null;
+    }
+
+    @Override
+    public Object call() throws Exception {
+        sw.asyncSend(syncMsg, xid);
+        OFBarrierRequest barrierMsg = new OFBarrierRequest();
+        sw.asyncSend(barrierMsg, xid);
+        latch.await();
+        return result;
+    }
+
+    public Integer getXid() {
+        return this.xid;
+    }
+
+    public void wakeup() {
+        this.latch.countDown();
+    }
+
+    public void wakeup(OFError e) {
+        result = e;
+        wakeup();
+    }
+
+}
\ No newline at end of file
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/Activator.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/Activator.java
new file mode 100644 (file)
index 0000000..75ba174
--- /dev/null
@@ -0,0 +1,322 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import org.opendaylight.controller.sal.core.IContainerListener;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketListen;
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketMux;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimExternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimInternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFInventoryService;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsManager;
+import org.opendaylight.controller.protocol_plugin.openflow.IPluginReadServiceFilter;
+import org.opendaylight.controller.protocol_plugin.openflow.IRefreshInternalProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.ITopologyServiceShimListener;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.internal.Controller;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.discovery.IDiscoveryService;
+import org.opendaylight.controller.sal.flowprogrammer.IPluginInFlowProgrammerService;
+import org.opendaylight.controller.sal.inventory.IPluginInInventoryService;
+import org.opendaylight.controller.sal.inventory.IPluginOutInventoryService;
+import org.opendaylight.controller.sal.packet.IPluginInDataPacketService;
+import org.opendaylight.controller.sal.packet.IPluginOutDataPacketService;
+import org.opendaylight.controller.sal.reader.IPluginInReadService;
+import org.opendaylight.controller.sal.topology.IPluginInTopologyService;
+import org.opendaylight.controller.sal.topology.IPluginOutTopologyService;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+
+/**
+ * Openflow protocol plugin Activator
+ *
+ *
+ */
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { TopologyServices.class, DataPacketServices.class,
+                InventoryService.class, ReadService.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(TopologyServices.class)) {
+            // export the service to be used by SAL
+            c.setInterface(new String[] {
+                    IPluginInTopologyService.class.getName(),
+                    ITopologyServiceShimListener.class.getName() }, null);
+            // Hook the services coming in from SAL, as optional in
+            // case SAL is not yet there, could happen
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IPluginOutTopologyService.class).setCallbacks(
+                    "setPluginOutTopologyService",
+                    "unsetPluginOutTopologyService").setRequired(false));
+            c.add(createServiceDependency().setService(
+                    IRefreshInternalProvider.class).setCallbacks(
+                    "setRefreshInternalProvider",
+                    "unsetRefreshInternalProvider").setRequired(false));
+        }
+
+        if (imp.equals(InventoryService.class)) {
+            // export the service
+            c.setInterface(new String[] {
+                    IPluginInInventoryService.class.getName(),
+                    IOFInventoryService.class.getName(),
+                    IInventoryShimInternalListener.class.getName() }, null);
+
+            // Now lets add a service dependency to make sure the
+            // provider of service exists
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IPluginOutInventoryService.class).setCallbacks(
+                    "setPluginOutInventoryServices",
+                    "unsetPluginOutInventoryServices").setRequired(false));
+        }
+
+        if (imp.equals(DataPacketServices.class)) {
+            // export the service to be used by SAL
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            // Set the protocolPluginType property which will be used
+            // by SAL
+            props.put("protocolPluginType", Node.NodeIDType.OPENFLOW);
+            c.setInterface(IPluginInDataPacketService.class.getName(), props);
+            // Hook the services coming in from SAL, as optional in
+            // case SAL is not yet there, could happen
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+            // This is required for the transmission to happen properly
+            c.add(createServiceDependency().setService(IDataPacketMux.class)
+                    .setCallbacks("setIDataPacketMux", "unsetIDataPacketMux")
+                    .setRequired(true));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IPluginOutDataPacketService.class).setCallbacks(
+                    "setPluginOutDataPacketService",
+                    "unsetPluginOutDataPacketService").setRequired(false));
+        }
+
+        if (imp.equals(ReadService.class)) {
+            // export the service to be used by SAL
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            // Set the protocolPluginType property which will be used
+            // by SAL
+            props.put("protocolPluginType", Node.NodeIDType.OPENFLOW);
+            c.setInterface(IPluginInReadService.class.getName(), props);
+            c.add(createServiceDependency().setService(
+                    IPluginReadServiceFilter.class).setCallbacks("setService",
+                    "unsetService").setRequired(true));
+        }
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services that are container
+     * independent.
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getGlobalImplementations() {
+        Object[] res = { Controller.class, OFStatisticsManager.class,
+                FlowProgrammerService.class, ReadServiceFilter.class,
+                DiscoveryService.class, DataPacketMuxDemux.class,
+                InventoryServiceShim.class, TopologyServiceShim.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     */
+    public void configureGlobalInstance(Component c, Object imp) {
+
+        if (imp.equals(Controller.class)) {
+            logger.debug("Activator configureGlobalInstance( ) is called");
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            props.put("name", "Controller");
+            c.setInterface(IController.class.getName(), props);
+        }
+
+        if (imp.equals(FlowProgrammerService.class)) {
+            // export the service to be used by SAL
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            // Set the protocolPluginType property which will be used
+            // by SAL
+            props.put("protocolPluginType", Node.NodeIDType.OPENFLOW);
+            c.setInterface(IPluginInFlowProgrammerService.class
+                           .getName(), props);
+
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+        }
+
+        if (imp.equals(ReadServiceFilter.class)) {
+
+            c.setInterface(new String[] {
+                    IPluginReadServiceFilter.class.getName(),
+                    IContainerListener.class.getName() }, null);
+
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+            c.add(createServiceDependency().setService(
+                    IOFStatisticsManager.class).setCallbacks("setService",
+                    "unsetService").setRequired(true));
+        }
+
+        if (imp.equals(OFStatisticsManager.class)) {
+
+            c.setInterface(new String[] { IOFStatisticsManager.class.getName(),
+                    IInventoryShimExternalListener.class.getName() }, null);
+
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+        }
+
+        if (imp.equals(DiscoveryService.class)) {
+            // export the service
+            c.setInterface(new String[] {
+                    IInventoryShimExternalListener.class.getName(),
+                    IDataPacketListen.class.getName(),
+                    IContainerListener.class.getName() }, null);
+
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+            c.add(createContainerServiceDependency(
+                    GlobalConstants.DEFAULT.toString()).setService(
+                    IPluginInInventoryService.class).setCallbacks(
+                    "setPluginInInventoryService",
+                    "unsetPluginInInventoryService").setRequired(true));
+            c.add(createServiceDependency().setService(IDataPacketMux.class)
+                    .setCallbacks("setIDataPacketMux", "unsetIDataPacketMux")
+                    .setRequired(true));
+            c.add(createServiceDependency().setService(IDiscoveryService.class)
+                    .setCallbacks("setDiscoveryService",
+                            "unsetDiscoveryService").setRequired(true));
+        }
+
+        // DataPacket mux/demux services, which is teh actual engine
+        // doing the packet switching
+        if (imp.equals(DataPacketMuxDemux.class)) {
+            c.setInterface(new String[] { IDataPacketMux.class.getName(),
+                    IContainerListener.class.getName(),
+                    IInventoryShimExternalListener.class.getName() }, null);
+
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+            c.add(createServiceDependency().setService(
+                    IPluginOutDataPacketService.class).setCallbacks(
+                    "setPluginOutDataPacketService",
+                    "unsetPluginOutDataPacketService").setRequired(false));
+            // See if there is any local packet dispatcher
+            c.add(createServiceDependency().setService(IDataPacketListen.class)
+                    .setCallbacks("setIDataPacketListen",
+                            "unsetIDataPacketListen").setRequired(false));
+        }
+
+        if (imp.equals(InventoryServiceShim.class)) {
+            c.setInterface(new String[] { IContainerListener.class.getName() },
+                    null);
+
+            c.add(createServiceDependency().setService(IController.class,
+                    "(name=Controller)").setCallbacks("setController",
+                    "unsetController").setRequired(true));
+            c.add(createServiceDependency().setService(
+                    IInventoryShimInternalListener.class).setCallbacks(
+                    "setInventoryShimInternalListener",
+                    "unsetInventoryShimInternalListener").setRequired(true));
+            c.add(createServiceDependency().setService(
+                    IInventoryShimExternalListener.class).setCallbacks(
+                    "setInventoryShimExternalListener",
+                    "unsetInventoryShimExternalListener").setRequired(false));
+            c.add(createServiceDependency().setService(
+                    IOFStatisticsManager.class).setCallbacks(
+                    "setStatisticsManager", "unsetStatisticsManager")
+                    .setRequired(false));
+        }
+
+        if (imp.equals(TopologyServiceShim.class)) {
+            c.setInterface(new String[] { IDiscoveryService.class.getName(),
+                    IContainerListener.class.getName(),
+                    IRefreshInternalProvider.class.getName() }, null);
+            c.add(createServiceDependency().setService(
+                    ITopologyServiceShimListener.class).setCallbacks(
+                    "setTopologyServiceShimListener",
+                    "unsetTopologyServiceShimListener").setRequired(true));
+            c.add(createServiceDependency().setService(
+                    IOFStatisticsManager.class).setCallbacks(
+                    "setStatisticsManager", "unsetStatisticsManager")
+                    .setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DataPacketMuxDemux.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DataPacketMuxDemux.java
new file mode 100644 (file)
index 0000000..93e7840
--- /dev/null
@@ -0,0 +1,405 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketListen;
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketMux;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimExternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IMessageListener;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.packet.IPluginOutDataPacketService;
+import org.opendaylight.controller.sal.packet.PacketResult;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+
+public class DataPacketMuxDemux implements IContainerListener,
+        IMessageListener, IDataPacketMux, IInventoryShimExternalListener {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(DataPacketMuxDemux.class);
+    private IController controller = null;
+    private ConcurrentMap<Long, ISwitch> swID2ISwitch = new ConcurrentHashMap<Long, ISwitch>();
+    // Gives a map between a Container and all the DataPacket listeners on SAL
+    private ConcurrentMap<String, IPluginOutDataPacketService> pluginOutDataPacketServices = new ConcurrentHashMap<String, IPluginOutDataPacketService>();
+    // Gives a map between a NodeConnector and the containers to which it belongs
+    private ConcurrentMap<NodeConnector, List<String>> nc2Container = new ConcurrentHashMap<NodeConnector, List<String>>();
+    // Gives a map between a Container and the FlowSpecs on it
+    private ConcurrentMap<String, List<ContainerFlow>> container2FlowSpecs = new ConcurrentHashMap<String, List<ContainerFlow>>();
+    // Track local data packet listener
+    private List<IDataPacketListen> iDataPacketListen = new CopyOnWriteArrayList<IDataPacketListen>();
+
+    void setIDataPacketListen(IDataPacketListen s) {
+        if (this.iDataPacketListen != null) {
+            if (!this.iDataPacketListen.contains(s)) {
+                logger.debug("Added new IDataPacketListen");
+                this.iDataPacketListen.add(s);
+            }
+        }
+    }
+
+    void unsetIDataPacketListen(IDataPacketListen s) {
+        if (this.iDataPacketListen != null) {
+            if (this.iDataPacketListen.contains(s)) {
+                logger.debug("Removed IDataPacketListen");
+                this.iDataPacketListen.remove(s);
+            }
+        }
+    }
+
+    void setPluginOutDataPacketService(Map<String, Object> props,
+            IPluginOutDataPacketService s) {
+        if (props == null) {
+            logger.error("Didn't receive the service properties");
+            return;
+        }
+        String containerName = (String) props.get("containerName");
+        if (containerName == null) {
+            logger.error("containerName not supplied");
+            return;
+        }
+        if (this.pluginOutDataPacketServices != null) {
+            // It's expected only one SAL per container as long as the
+            // replication is done in the SAL implementation toward
+            // the different APPS
+            this.pluginOutDataPacketServices.put(containerName, s);
+            logger.debug("New outService for container:" + containerName);
+        }
+    }
+
+    void unsetPluginOutDataPacketService(Map<String, Object> props,
+            IPluginOutDataPacketService s) {
+        if (props == null) {
+            logger.error("Didn't receive the service properties");
+            return;
+        }
+        String containerName = (String) props.get("containerName");
+        if (containerName == null) {
+            logger.error("containerName not supplied");
+            return;
+        }
+        if (this.pluginOutDataPacketServices != null) {
+            this.pluginOutDataPacketServices.remove(containerName);
+            logger.debug("Removed outService for container:" + containerName);
+        }
+    }
+
+    void setController(IController s) {
+        logger.debug("Controller provider set in DATAPACKET SERVICES");
+        this.controller = s;
+    }
+
+    void unsetController(IController s) {
+        if (this.controller == s) {
+            logger.debug("Controller provider UNset in DATAPACKET SERVICES");
+            this.controller = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        this.controller.addMessageListener(OFType.PACKET_IN, this);
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        this.controller.removeMessageListener(OFType.PACKET_IN, this);
+
+        // Clear state that may need to be reused on component restart
+        this.pluginOutDataPacketServices.clear();
+        this.nc2Container.clear();
+        this.container2FlowSpecs.clear();
+        this.controller = null;
+        this.swID2ISwitch.clear();
+    }
+
+    @Override
+    public void receive(ISwitch sw, OFMessage msg) {
+        if (sw == null || msg == null
+                || this.pluginOutDataPacketServices == null) {
+            // Something fishy, we cannot do anything
+            return;
+        }
+        if (msg instanceof OFPacketIn) {
+            OFPacketIn ofPacket = (OFPacketIn) msg;
+            Long ofSwitchID = Long.valueOf(sw.getId());
+            Short ofPortID = Short.valueOf(ofPacket.getInPort());
+
+            try {
+                Node n = new Node(Node.NodeIDType.OPENFLOW, ofSwitchID);
+                NodeConnector p = PortConverter.toNodeConnector(ofPortID, n);
+                RawPacket dataPacket = new RawPacket(ofPacket.getPacketData());
+                dataPacket.setIncomingNodeConnector(p);
+
+                // Try to dispatch the packet locally, in here we will
+                // pass the parsed packet simply because once the
+                // packet infra is settled all the packets will passed
+                // around as parsed and read-only
+                for (int i = 0; i < this.iDataPacketListen.size(); i++) {
+                    IDataPacketListen s = this.iDataPacketListen.get(i);
+                    if (s.receiveDataPacket(dataPacket).equals(
+                            PacketResult.CONSUME)) {
+                        logger.trace("Consumed locally data packet");
+                        return;
+                    }
+                }
+
+                // Now dispatch the packet toward SAL at least for
+                // default container, we need to revisit this in a general
+                // slicing architecture refresh
+                IPluginOutDataPacketService defaultOutService = this.pluginOutDataPacketServices
+                        .get(GlobalConstants.DEFAULT.toString());
+                if (defaultOutService != null) {
+                    defaultOutService.receiveDataPacket(dataPacket);
+                    logger.trace("Dispatched to apps a frame of size: "
+                            + ofPacket.getPacketData().length
+                            + " on container: "
+                            + GlobalConstants.DEFAULT.toString());
+                }
+                // Now check the mapping between nodeConnector and
+                // Container and later on optinally filter based on
+                // flowSpec
+                List<String> containersRX = this.nc2Container.get(p);
+                if (containersRX != null) {
+                    for (int i = 0; i < containersRX.size(); i++) {
+                        String container = containersRX.get(i);
+                        IPluginOutDataPacketService s = this.pluginOutDataPacketServices
+                                .get(container);
+                        if (s != null) {
+                            // TODO add filtering on a per-flowSpec base
+                            s.receiveDataPacket(dataPacket);
+                            logger.trace("Dispatched to apps a frame of size: "
+                                    + ofPacket.getPacketData().length
+                                    + " on container: " + container);
+
+                        }
+                    }
+                }
+
+                // This is supposed to be the catch all for all the
+                // DataPacket hence we will assume it has been handled
+                return;
+            } catch (ConstructionException cex) {
+            }
+
+            // If we reach this point something went wrong.
+            return;
+        } else {
+            // We don't care about non-data packets
+            return;
+        }
+    }
+
+    @Override
+    public void transmitDataPacket(RawPacket outPkt) {
+        // Sanity check area
+        if (outPkt == null) {
+            return;
+        }
+
+        NodeConnector outPort = outPkt.getOutgoingNodeConnector();
+        if (outPort == null) {
+            return;
+        }
+
+        if (!outPort.getType().equals(
+                NodeConnector.NodeConnectorIDType.OPENFLOW)) {
+            // The output Port is not of type OpenFlow
+            return;
+        }
+
+        Short port = (Short) outPort.getID();
+        Long swID = (Long) outPort.getNode().getID();
+        ISwitch sw = this.swID2ISwitch.get(swID);
+
+        if (sw == null) {
+            // If we cannot get the controller descriptor we cannot even
+            // send out the frame
+            return;
+        }
+
+        byte[] data = outPkt.getPacketData();
+        // build action
+        OFActionOutput action = new OFActionOutput().setPort(port);
+        // build packet out
+        OFPacketOut po = new OFPacketOut().setBufferId(
+                OFPacketOut.BUFFER_ID_NONE).setInPort(OFPort.OFPP_NONE)
+                .setActions(Collections.singletonList((OFAction) action))
+                .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+
+        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength()
+                + data.length);
+        po.setPacketData(data);
+
+        sw.asyncSend(po);
+        logger.trace("Transmitted a frame of size:" + data.length);
+    }
+
+    public void addNode(Node node, Set<Property> props) {
+        if (node == null)
+            return;
+
+        long sid = (Long) node.getID();
+        ISwitch sw = controller.getSwitches().get(sid);
+        if (sw != null) {
+            this.swID2ISwitch.put(sw.getId(), sw);
+        }
+    }
+
+    public void removeNode(Node node) {
+        if (node == null)
+            return;
+
+        long sid = (Long) node.getID();
+        ISwitch sw = controller.getSwitches().get(sid);
+        if (sw != null) {
+            this.swID2ISwitch.remove(sw.getId());
+        }
+    }
+
+    @Override
+    public void tagUpdated(String containerName, Node n, short oldTag,
+            short newTag, UpdateType t) {
+        // Do nothing
+    }
+
+    @Override
+    public void containerFlowUpdated(String containerName,
+            ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) {
+        if (this.container2FlowSpecs == null) {
+            logger.error("container2FlowSpecs is NULL");
+            return;
+        }
+        List<ContainerFlow> fSpecs = this.container2FlowSpecs
+                .get(containerName);
+        if (fSpecs == null) {
+            fSpecs = new CopyOnWriteArrayList<ContainerFlow>();
+        }
+        boolean updateMap = false;
+        switch (t) {
+        case ADDED:
+            if (!fSpecs.contains(previousFlow)) {
+                fSpecs.add(previousFlow);
+            }
+            break;
+        case REMOVED:
+            if (fSpecs.contains(previousFlow)) {
+                fSpecs.remove(previousFlow);
+            }
+            break;
+        case CHANGED:
+            break;
+        }
+        if (updateMap) {
+            if (fSpecs.isEmpty()) {
+                this.container2FlowSpecs.remove(containerName);
+            } else {
+                this.container2FlowSpecs.put(containerName, fSpecs);
+            }
+        }
+    }
+
+    @Override
+    public void nodeConnectorUpdated(String containerName, NodeConnector p,
+            UpdateType t) {
+        if (this.nc2Container == null) {
+            logger.error("nc2Container is NULL");
+            return;
+        }
+        List<String> containers = this.nc2Container.get(p);
+        if (containers == null) {
+            containers = new CopyOnWriteArrayList<String>();
+        }
+        boolean updateMap = false;
+        switch (t) {
+        case ADDED:
+            if (!containers.contains(containerName)) {
+                containers.add(containerName);
+                updateMap = true;
+            }
+            break;
+        case REMOVED:
+            if (containers.contains(containerName)) {
+                containers.remove(containerName);
+                updateMap = true;
+            }
+            break;
+        case CHANGED:
+            break;
+        }
+        if (updateMap) {
+            if (containers.isEmpty()) {
+                // Do cleanup to reduce memory footprint if no
+                // elements to be tracked
+                this.nc2Container.remove(p);
+            } else {
+                this.nc2Container.put(p, containers);
+            }
+        }
+    }
+
+    @Override
+    public void containerModeUpdated(UpdateType t) {
+        // do nothing
+    }
+
+    @Override
+    public void updateNode(Node node, UpdateType type, Set<Property> props) {
+        switch (type) {
+        case ADDED:
+            addNode(node, props);
+            break;
+        case REMOVED:
+            removeNode(node);
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void updateNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props) {
+        // do nothing
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DataPacketServices.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DataPacketServices.java
new file mode 100644 (file)
index 0000000..adb9d20
--- /dev/null
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketMux;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.opendaylight.controller.sal.packet.IPluginInDataPacketService;
+import org.opendaylight.controller.sal.packet.RawPacket;
+
+public class DataPacketServices implements IPluginInDataPacketService {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(DataPacketServices.class);
+    private IDataPacketMux iDataPacketMux = null;
+
+    void setIDataPacketMux(IDataPacketMux s) {
+        this.iDataPacketMux = s;
+    }
+
+    void unsetIDataPacketMux(IDataPacketMux s) {
+        if (this.iDataPacketMux == s) {
+            this.iDataPacketMux = null;
+        }
+    }
+
+    @Override
+    public void transmitDataPacket(RawPacket outPkt) {
+        this.iDataPacketMux.transmitDataPacket(outPkt);
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DescStatisticsConverter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DescStatisticsConverter.java
new file mode 100644 (file)
index 0000000..33918c5
--- /dev/null
@@ -0,0 +1,47 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.List;
+
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+
+import org.opendaylight.controller.sal.reader.NodeDescription;
+
+/**
+ * Utility class for converting openflow description statistics
+ * into SAL NodeDescription object
+ *
+ *
+ *
+ */
+public class DescStatisticsConverter {
+    NodeDescription hwDesc;
+    OFDescriptionStatistics ofDesc;
+
+    public DescStatisticsConverter(List<OFStatistics> statsList) {
+        this.hwDesc = null;
+        this.ofDesc = (OFDescriptionStatistics) statsList.get(0);
+    }
+
+    public NodeDescription getHwDescription() {
+        if (hwDesc == null && ofDesc != null) {
+            hwDesc = new NodeDescription();
+            hwDesc.setManufacturer(ofDesc.getManufacturerDescription());
+            hwDesc.setHardware(ofDesc.getHardwareDescription());
+            hwDesc.setSoftware(ofDesc.getSoftwareDescription());
+            hwDesc.setSdnProtocolDescription(ofDesc.getDatapathDescription());
+            hwDesc.setSerialNumber(ofDesc.getSerialNumber());
+        }
+        return hwDesc;
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DiscoveryService.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/DiscoveryService.java
new file mode 100644 (file)
index 0000000..aa8c517
--- /dev/null
@@ -0,0 +1,1441 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketListen;
+import org.opendaylight.controller.protocol_plugin.openflow.IDataPacketMux;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimExternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFPhysicalPort;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Config;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.discovery.IDiscoveryService;
+import org.opendaylight.controller.sal.inventory.IPluginInInventoryService;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.LLDP;
+import org.opendaylight.controller.sal.packet.LLDPTLV;
+import org.opendaylight.controller.sal.packet.LinkEncap;
+import org.opendaylight.controller.sal.packet.PacketResult;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.NetUtils;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+/**
+ * The class describes neighbor discovery service for an OpenFlow network.
+ */
+public class DiscoveryService implements IInventoryShimExternalListener,
+        IDataPacketListen, IContainerListener, CommandProvider {
+    private static Logger logger = LoggerFactory
+            .getLogger(DiscoveryService.class);
+    private IController controller = null;
+    private IDiscoveryService discoveryService = null;
+    private IPluginInInventoryService pluginInInventoryService = null;
+    private IDataPacketMux iDataPacketMux = null;
+
+    private List<NodeConnector> readyListHi = null; // newly added ports go into this list and will be served first
+    private List<NodeConnector> readyListLo = null; // come here after served at least once
+    private List<NodeConnector> waitingList = null; // staging area during quiet period
+    private ConcurrentMap<NodeConnector, Integer> pendingMap = null;// wait for response back
+    private ConcurrentMap<NodeConnector, Edge> edgeMap = null; // openflow edges keyed by head connector
+    private ConcurrentMap<NodeConnector, Integer> agingMap = null; // aging entries keyed by edge port
+    private ConcurrentMap<NodeConnector, Edge> prodMap = null; // production edges keyed by edge port
+
+    private Timer discoveryTimer; // discovery timer
+    private DiscoveryTimerTask discoveryTimerTask; // timer task
+    private long discoveryTimerTick = 1L * 1000; // per tick in msec
+    private int discoveryTimerTickCount = 0; // main tick counter
+    private int discoveryBatchMaxPorts = 500; // max # of ports handled in one batch
+    private int discoveryBatchPauseTicks = 28; // pause a little bit after this point
+    private int discoveryBatchRestartTicks = 30; // periodically restart batching process
+    private int discoveryRetry = 1; // number of retry after initial timeout
+    private int discoveryTimeoutTicks = 2; // timeout 2 sec
+    private int discoveryAgeoutTicks = 120; // age out 2 min
+    private int discoveryConsistencyCheckMultiple = 2; // multiple of discoveryBatchRestartTicks
+    private int discoveryConsistencyCheckTickCount = discoveryBatchPauseTicks; // CC tick counter
+    private int discoveryConsistencyCheckCallingTimes = 0; // # of times CC gets called
+    private int discoveryConsistencyCheckCorrected = 0; // # of cases CC corrected
+    private boolean discoveryConsistencyCheckEnabled = true;// enable or disable CC
+    private boolean discoveryAgingEnabled = true; // enable or disable aging
+    private boolean discoverySpoofingEnabled = true; // enable or disable spoofing neighbor of a production network
+
+    private BlockingQueue<NodeConnector> transmitQ;
+    private Thread transmitThread;
+    private Boolean throttling = false; // if true, no more batching.
+    private volatile Boolean shuttingDown = false;
+
+    private LLDPTLV chassisIdTlv, portIdTlv, ttlTlv, customTlv;
+
+    class DiscoveryTransmit implements Runnable {
+        private final BlockingQueue<NodeConnector> transmitQ;
+
+        DiscoveryTransmit(BlockingQueue<NodeConnector> transmitQ) {
+            this.transmitQ = transmitQ;
+        }
+
+        public void run() {
+            while (true) {
+                try {
+                    NodeConnector nodeConnector = transmitQ.take();
+                    RawPacket outPkt = createDiscoveryPacket(nodeConnector);
+                    sendDiscoveryPacket(nodeConnector, outPkt);
+                    nodeConnector = null;
+                } catch (InterruptedException e1) {
+                    logger
+                            .warn("DiscoveryTransmit interupted", e1
+                                    .getMessage());
+                    if (shuttingDown)
+                        return;
+                } catch (Exception e2) {
+                    e2.printStackTrace();
+                }
+            }
+        }
+    }
+
+    class DiscoveryTimerTask extends TimerTask {
+        public void run() {
+            checkTimeout();
+            checkAging();
+            doConsistencyCheck();
+            doDiscovery();
+        }
+    }
+
+    private RawPacket createDiscoveryPacket(NodeConnector nodeConnector) {
+        String nodeId = HexEncode.longToHexString((Long) nodeConnector
+                .getNode().getID());
+
+        // Create LLDP ChassisID TLV
+        byte[] cidValue = LLDPTLV.createChassisIDTLVValue(nodeId);
+        chassisIdTlv.setType((byte) LLDPTLV.TLVType.ChassisID.getValue())
+                .setLength((short) cidValue.length).setValue(cidValue);
+
+        // Create LLDP PortID TLV
+        String portId = nodeConnector.getNodeConnectorIDString();
+        byte[] pidValue = LLDPTLV.createPortIDTLVValue(portId);
+        portIdTlv.setType((byte) LLDPTLV.TLVType.PortID.getValue())
+                .setLength((short) pidValue.length).setValue(pidValue);
+
+        // Create LLDP Custom TLV
+        byte[] customValue = LLDPTLV.createCustomTLVValue(nodeConnector.toString());
+        customTlv.setType((byte) LLDPTLV.TLVType.Custom.getValue())
+                .setLength((short) customValue.length).setValue(customValue);
+
+        // Create LLDP Custom Option list
+        List<LLDPTLV> customList = new ArrayList<LLDPTLV>();
+        customList.add(customTlv);
+
+        // Create discovery pkt
+        LLDP discoveryPkt = new LLDP();
+        discoveryPkt.setChassisId(chassisIdTlv).setPortId(portIdTlv).setTtl(
+                ttlTlv).setOptionalTLVList(customList);
+
+        RawPacket rawPkt = null;
+        try {
+            // Create ethernet pkt
+               byte[] sourceMac = getSouceMACFromNodeID(nodeId);
+            Ethernet ethPkt = new Ethernet();
+            ethPkt.setSourceMACAddress(sourceMac).setDestinationMACAddress(
+                    LLDP.LLDPMulticastMac).setEtherType(
+                    EtherTypes.LLDP.shortValue()).setPayload(discoveryPkt);
+
+            byte[] data = ethPkt.serialize();
+            rawPkt = new RawPacket(data);
+            rawPkt.setOutgoingNodeConnector(nodeConnector);
+        } catch (ConstructionException cex) {
+            logger.debug("RawPacket creation caught exception {}", cex
+                    .getMessage());
+        } catch (Exception e) {
+            logger.error("Failed to serialize the LLDP packet: " + e);
+        }
+
+        return rawPkt;
+    }
+
+    private void sendDiscoveryPacket(NodeConnector nodeConnector,
+            RawPacket outPkt) {
+        if (nodeConnector == null) {
+            logger.error("nodeConnector is null");
+            return;
+        }
+
+        if (outPkt == null) {
+            logger.error("outPkt is null");
+            return;
+        }
+
+        long sid = (Long) nodeConnector.getNode().getID();
+        ISwitch sw = controller.getSwitches().get(sid);
+
+        if (sw == null) {
+            logger.error("Switch of swid {} is null", sid);
+            return;
+        }
+
+        if (!sw.isOperational()) {
+            logger.error("Switch {} is not operational", sw);
+            return;
+        }
+
+        if (this.iDataPacketMux == null) {
+            logger.error("Cannot send discover packets out");
+            return;
+        }
+
+        logger.trace("Sending topology discovery pkt thru {}", nodeConnector);
+        this.iDataPacketMux.transmitDataPacket(outPkt);
+    }
+
+    @Override
+    public PacketResult receiveDataPacket(RawPacket inPkt) {
+        if (inPkt == null) {
+            logger.debug("Ignoring null packet");
+            return PacketResult.IGNORED;
+        }
+
+        byte[] data = inPkt.getPacketData();
+        if (data.length <= 0) {
+            logger.trace("Ignoring zero length packet");
+            return PacketResult.IGNORED;
+        }
+
+        if (!inPkt.getEncap().equals(LinkEncap.ETHERNET)) {
+            logger.trace("Ignoring non ethernet packet");
+            return PacketResult.IGNORED;
+        }
+
+        if (((Short) inPkt.getIncomingNodeConnector().getID())
+                .equals(NodeConnector.SPECIALNODECONNECTORID)) {
+            logger.trace("Ignoring ethernet packet received on special port: "
+                    + inPkt.getIncomingNodeConnector().toString());
+            return PacketResult.IGNORED;
+        }
+
+        Ethernet ethPkt = new Ethernet();
+        try {
+            ethPkt.deserialize(data, 0, data.length * NetUtils.NumBitsInAByte);
+        } catch (Exception e) {
+            logger.warn("Failed to decode LLDP packet from "
+                    + inPkt.getIncomingNodeConnector() + ": " + e);
+            return PacketResult.IGNORED;
+        }
+        if (ethPkt.getPayload() instanceof LLDP) {
+            NodeConnector dst = inPkt.getIncomingNodeConnector();
+            if (!processDiscoveryPacket(dst, ethPkt)) {
+                /* Spoof the discovery pkt if not generated from us */
+                spoofDiscoveryPacket(dst, ethPkt);
+            }
+            return PacketResult.CONSUME;
+        }
+        return PacketResult.IGNORED;
+    }
+
+    /*
+     * Spoof incoming discovery frames generated by the production network neighbor switch
+     */
+    private void spoofDiscoveryPacket(NodeConnector dstNodeConnector,
+            Ethernet ethPkt) {
+        if (!this.discoverySpoofingEnabled) {
+            return;
+        }
+
+        if ((dstNodeConnector == null) || (ethPkt == null)) {
+            logger
+                    .trace("Ignoring processing of discovery packet: Null node connector or packet");
+            return;
+        }
+
+        logger.trace("Handle discovery packet {} from {}", ethPkt,
+                dstNodeConnector);
+
+        LLDP lldp = (LLDP) ethPkt.getPayload();
+
+        try {
+            String nodeId = LLDPTLV.getHexStringValue(lldp.getChassisId().getValue(), lldp.getChassisId().getLength());
+            String portId = LLDPTLV.getStringValue(lldp.getPortId().getValue(), lldp.getPortId().getLength());
+            Node srcNode = new Node(Node.NodeIDType.PRODUCTION, nodeId);
+            NodeConnector srcNodeConnector = NodeConnectorCreator
+                    .createNodeConnector(
+                            NodeConnector.NodeConnectorIDType.PRODUCTION,
+                            portId, srcNode);
+
+            // push it out to Topology
+            Edge edge = null;
+            Set<Property> props = null;
+            try {
+                edge = new Edge(srcNodeConnector, dstNodeConnector);
+                props = getProps(dstNodeConnector);
+            } catch (ConstructionException e) {
+                logger.error(e.getMessage());
+            }
+            addEdge(edge, props);
+
+            logger.trace("Received discovery packet for Edge {}", edge);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /*
+     * Handle discovery frames generated by our controller
+     * @return true if it's a success
+     */
+    private boolean processDiscoveryPacket(NodeConnector dstNodeConnector,
+            Ethernet ethPkt) {
+        if ((dstNodeConnector == null) || (ethPkt == null)) {
+            logger
+                    .trace("Ignoring processing of discovery packet: Null node connector or packet");
+            return false;
+        }
+
+        logger.trace("Handle discovery packet {} from {}", ethPkt,
+                dstNodeConnector);
+
+        LLDP lldp = (LLDP) ethPkt.getPayload();
+
+        List<LLDPTLV> optionalTLVList = lldp.getOptionalTLVList();
+        if (optionalTLVList == null) {
+            logger.info("The discovery packet with null custom option from {}",
+                    dstNodeConnector);
+            return false;
+        }
+
+        Node srcNode = null;
+        NodeConnector srcNodeConnector = null;
+        for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) {
+            if (lldptlv.getType() == LLDPTLV.TLVType.Custom.getValue()) {
+               String ncString = LLDPTLV.getCustomString(lldptlv.getValue(), lldptlv.getLength());
+               srcNodeConnector = NodeConnector.fromString(ncString);
+               if (srcNodeConnector != null) {
+                       srcNode = srcNodeConnector.getNode();
+                       /* Check if it's expected */
+                       if (isTracked(srcNodeConnector)) {
+                               break;
+                    } else {
+                       srcNode = null;
+                       srcNodeConnector = null;
+                    }
+                }
+            }
+        }
+
+        if ((srcNode == null) || (srcNodeConnector == null)) {
+            logger
+                    .trace(
+                            "Received non-controller generated discovery packet from {}",
+                            dstNodeConnector);
+            return false;
+        }
+
+        // push it out to Topology
+        Edge edge = null;
+        Set<Property> props = null;
+        try {
+            edge = new Edge(srcNodeConnector, dstNodeConnector);
+            props = getProps(dstNodeConnector);
+        } catch (ConstructionException e) {
+            logger.error(e.getMessage());
+        }
+        addEdge(edge, props);
+
+        logger.trace("Received discovery packet for Edge {}", edge);
+
+        return true;
+    }
+
+    public Map<String, Property> getPropMap(NodeConnector nodeConnector) {
+        if (nodeConnector == null) {
+            return null;
+        }
+
+        if (pluginInInventoryService == null) {
+            return null;
+        }
+
+        Map<NodeConnector, Map<String, Property>> props = pluginInInventoryService
+                .getNodeConnectorProps(false);
+        if (props == null) {
+            return null;
+        }
+
+        return props.get(nodeConnector);
+    }
+
+    public Property getProp(NodeConnector nodeConnector, String propName) {
+        Map<String, Property> propMap = getPropMap(nodeConnector);
+        if (propMap == null) {
+            return null;
+        }
+
+        Property prop = (Property) propMap.get(propName);
+        return prop;
+    }
+
+    public Set<Property> getProps(NodeConnector nodeConnector) {
+        Map<String, Property> propMap = getPropMap(nodeConnector);
+        if (propMap == null) {
+            return null;
+        }
+
+        Set<Property> props = new HashSet<Property>(propMap.values());
+        return props;
+    }
+
+    private boolean isEnabled(NodeConnector nodeConnector) {
+        if (nodeConnector == null) {
+            return false;
+        }
+
+        Config config = (Config) getProp(nodeConnector, Config.ConfigPropName);
+        State state = (State) getProp(nodeConnector, State.StatePropName);
+        return ((config != null) && (config.getValue() == Config.ADMIN_UP)
+                && (state != null) && (state.getValue() == State.EDGE_UP));
+    }
+
+    private boolean isTracked(NodeConnector nodeConnector) {
+        if (readyListHi.contains(nodeConnector)) {
+            return true;
+        }
+
+        if (readyListLo.contains(nodeConnector)) {
+            return true;
+        }
+
+        if (pendingMap.keySet().contains(nodeConnector)) {
+            return true;
+        }
+
+        if (waitingList.contains(nodeConnector)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private Set<NodeConnector> getWorkingSet() {
+        Set<NodeConnector> workingSet = new HashSet<NodeConnector>();
+        Set<NodeConnector> removeSet = new HashSet<NodeConnector>();
+
+        for (NodeConnector nodeConnector : readyListHi) {
+            if (isOverLimit(workingSet.size())) {
+                break;
+            }
+
+            workingSet.add(nodeConnector);
+            removeSet.add(nodeConnector);
+        }
+        readyListHi.removeAll(removeSet);
+
+        removeSet.clear();
+        for (NodeConnector nodeConnector : readyListLo) {
+            if (isOverLimit(workingSet.size())) {
+                break;
+            }
+
+            workingSet.add(nodeConnector);
+            removeSet.add(nodeConnector);
+        }
+        readyListLo.removeAll(removeSet);
+
+        return workingSet;
+    }
+
+    private Boolean isOverLimit(int size) {
+        return ((size >= discoveryBatchMaxPorts) && !throttling);
+    }
+
+    private void addDiscovery() {
+        Map<Long, ISwitch> switches = controller.getSwitches();
+        Set<Long> sidSet = switches.keySet();
+        if (sidSet == null) {
+            return;
+        }
+        for (Long sid : sidSet) {
+            Node node = NodeCreator.createOFNode(sid);
+            addDiscovery(node);
+        }
+    }
+
+    private void addDiscovery(Node node) {
+        Map<Long, ISwitch> switches = controller.getSwitches();
+        ISwitch sw = switches.get((Long) node.getID());
+        List<OFPhysicalPort> ports = sw.getEnabledPorts();
+        if (ports == null) {
+            return;
+        }
+        for (OFPhysicalPort port : ports) {
+            NodeConnector nodeConnector = NodeConnectorCreator
+                    .createOFNodeConnector(port.getPortNumber(), node);
+            if (!readyListHi.contains(nodeConnector)) {
+                readyListHi.add(nodeConnector);
+            }
+        }
+    }
+
+    private void addDiscovery(NodeConnector nodeConnector) {
+        if (isTracked(nodeConnector)) {
+            return;
+        }
+
+        readyListHi.add(nodeConnector);
+    }
+
+    private Set<NodeConnector> getRemoveSet(Collection<NodeConnector> c,
+            Node node) {
+        Set<NodeConnector> removeSet = new HashSet<NodeConnector>();
+        if (c == null) {
+            return removeSet;
+        }
+        for (NodeConnector nodeConnector : c) {
+            if (node.equals(nodeConnector.getNode())) {
+                removeSet.add(nodeConnector);
+            }
+        }
+        return removeSet;
+    }
+
+    private void removeDiscovery(Node node) {
+        Set<NodeConnector> removeSet;
+
+        removeSet = getRemoveSet(readyListHi, node);
+        readyListHi.removeAll(removeSet);
+
+        removeSet = getRemoveSet(readyListLo, node);
+        readyListLo.removeAll(removeSet);
+
+        removeSet = getRemoveSet(waitingList, node);
+        waitingList.removeAll(removeSet);
+
+        removeSet = getRemoveSet(pendingMap.keySet(), node);
+        for (NodeConnector nodeConnector : removeSet) {
+            pendingMap.remove(nodeConnector);
+        }
+
+        removeSet = getRemoveSet(edgeMap.keySet(), node);
+        for (NodeConnector nodeConnector : removeSet) {
+            removeEdge(nodeConnector, false);
+        }
+
+        removeSet = getRemoveSet(prodMap.keySet(), node);
+        for (NodeConnector nodeConnector : removeSet) {
+            removeProd(nodeConnector);
+        }
+    }
+
+    private void removeDiscovery(NodeConnector nodeConnector) {
+        readyListHi.remove(nodeConnector);
+        readyListLo.remove(nodeConnector);
+        waitingList.remove(nodeConnector);
+        pendingMap.remove(nodeConnector);
+        removeEdge(nodeConnector, false);
+        removeProd(nodeConnector);
+    }
+
+    private void checkTimeout() {
+        Set<NodeConnector> removeSet = new HashSet<NodeConnector>();
+        Set<NodeConnector> retrySet = new HashSet<NodeConnector>();
+        int sentCount;
+
+        Set<NodeConnector> pendingSet = pendingMap.keySet();
+        if (pendingSet != null) {
+            for (NodeConnector nodeConnector : pendingSet) {
+                sentCount = pendingMap.get(nodeConnector);
+                pendingMap.put(nodeConnector, ++sentCount);
+                if (sentCount > getDiscoveryFinalTimeoutInterval()) {
+                    // timeout the edge
+                    removeSet.add(nodeConnector);
+                    logger.trace("Discovery timeout {}", nodeConnector);
+                } else if (sentCount % discoveryTimeoutTicks == 0) {
+                    retrySet.add(nodeConnector);
+                }
+            }
+        }
+
+        for (NodeConnector nodeConnector : removeSet) {
+            removeEdge(nodeConnector);
+        }
+
+        for (NodeConnector nodeConnector : retrySet) {
+            transmitQ.add(nodeConnector);
+        }
+    }
+
+    private void checkAging() {
+        if (!discoveryAgingEnabled) {
+            return;
+        }
+
+        Set<NodeConnector> removeSet = new HashSet<NodeConnector>();
+        int sentCount;
+
+        Set<NodeConnector> agingSet = agingMap.keySet();
+        if (agingSet != null) {
+            for (NodeConnector nodeConnector : agingSet) {
+                sentCount = agingMap.get(nodeConnector);
+                agingMap.put(nodeConnector, ++sentCount);
+                if (sentCount > discoveryAgeoutTicks) {
+                    // age out the edge
+                    removeSet.add(nodeConnector);
+                    logger.trace("Discovery age out {}", nodeConnector);
+                }
+            }
+        }
+
+        for (NodeConnector nodeConnector : removeSet) {
+            removeProd(nodeConnector);
+        }
+    }
+
+    private void doDiscovery() {
+        if (++discoveryTimerTickCount <= discoveryBatchPauseTicks) {
+            for (NodeConnector nodeConnector : getWorkingSet()) {
+                pendingMap.put(nodeConnector, 0);
+                transmitQ.add(nodeConnector);
+            }
+        } else if (discoveryTimerTickCount >= discoveryBatchRestartTicks) {
+            discoveryTimerTickCount = 0;
+            for (NodeConnector nodeConnector : waitingList) {
+                if (!readyListLo.contains(nodeConnector))
+                    readyListLo.add(nodeConnector);
+            }
+            waitingList.removeAll(readyListLo);
+        }
+    }
+
+    private void doConsistencyCheck() {
+        if (!discoveryConsistencyCheckEnabled) {
+            return;
+        }
+
+        if (++discoveryConsistencyCheckTickCount
+                % getDiscoveryConsistencyCheckInterval() != 0) {
+            return;
+        }
+
+        discoveryConsistencyCheckCallingTimes++;
+
+        Set<NodeConnector> removeSet = new HashSet<NodeConnector>();
+        Set<NodeConnector> ncSet = edgeMap.keySet();
+        if (ncSet == null) {
+            return;
+        }
+        for (NodeConnector nodeConnector : ncSet) {
+            if (!isEnabled(nodeConnector)) {
+                removeSet.add(nodeConnector);
+                discoveryConsistencyCheckCorrected++;
+                logger.debug("ConsistencyChecker: remove disabled {}",
+                        nodeConnector);
+                continue;
+            }
+
+            if (!isTracked(nodeConnector)) {
+                waitingList.add(nodeConnector);
+                discoveryConsistencyCheckCorrected++;
+                logger.debug("ConsistencyChecker: add back untracked {}",
+                        nodeConnector);
+                continue;
+            }
+        }
+
+        for (NodeConnector nodeConnector : removeSet) {
+            removeEdge(nodeConnector, false);
+        }
+
+        // remove stale entries
+        removeSet.clear();
+        for (NodeConnector nodeConnector : waitingList) {
+            if (!isEnabled(nodeConnector)) {
+                removeSet.add(nodeConnector);
+                discoveryConsistencyCheckCorrected++;
+                logger.debug("ConsistencyChecker: remove disabled {}",
+                        nodeConnector);
+            }
+        }
+        waitingList.removeAll(removeSet);
+
+        // Get a snapshot of all the existing switches
+        Map<Long, ISwitch> switches = this.controller.getSwitches();
+        for (ISwitch sw : switches.values()) {
+            for (OFPhysicalPort port : sw.getEnabledPorts()) {
+                Node node = NodeCreator.createOFNode(sw.getId());
+                NodeConnector nodeConnector = NodeConnectorCreator
+                        .createOFNodeConnector(port.getPortNumber(), node);
+                if (!isTracked(nodeConnector)) {
+                    waitingList.add(nodeConnector);
+                    discoveryConsistencyCheckCorrected++;
+                    logger.debug("ConsistencyChecker: add back untracked {}",
+                            nodeConnector);
+                }
+            }
+        }
+    }
+
+    private void addEdge(Edge edge, Set<Property> props) {
+        if (edge == null) {
+            return;
+        }
+
+        NodeConnector src = edge.getTailNodeConnector();
+        if (!src.getType().equals(
+                NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+            pendingMap.remove(src);
+            if (!waitingList.contains(src)) {
+                waitingList.add(src);
+            }
+        } else {
+            NodeConnector dst = edge.getHeadNodeConnector();
+            agingMap.put(dst, 0);
+        }
+
+        // notify routeEngine
+        updateEdge(edge, UpdateType.ADDED, props);
+        logger.trace("Add edge {}", edge);
+    }
+
+    /*
+     * Remove Production edge
+     */
+    private void removeProd(NodeConnector nodeConnector) {
+        agingMap.remove(nodeConnector);
+
+        Edge edge = null;
+        Set<NodeConnector> prodKeySet = prodMap.keySet();
+        if ((prodKeySet != null) && (prodKeySet.contains(nodeConnector))) {
+            edge = prodMap.get(nodeConnector);
+            prodMap.remove(nodeConnector);
+        }
+
+        // notify Topology
+        if (this.discoveryService != null) {
+            this.discoveryService.notifyEdge(edge, UpdateType.REMOVED, null);
+        }
+        logger.trace("Remove {}", nodeConnector);
+    }
+
+    /*
+     * Remove OpenFlow edge
+     */
+    private void removeEdge(NodeConnector nodeConnector, boolean stillEnabled) {
+        pendingMap.remove(nodeConnector);
+        readyListLo.remove(nodeConnector);
+        readyListHi.remove(nodeConnector);
+
+        if (stillEnabled) {
+            // keep discovering
+            if (!waitingList.contains(nodeConnector)) {
+                waitingList.add(nodeConnector);
+            }
+        } else {
+            // stop it
+            waitingList.remove(nodeConnector);
+        }
+
+        Edge edge = null;
+        Set<NodeConnector> edgeKeySet = edgeMap.keySet();
+        if ((edgeKeySet != null) && (edgeKeySet.contains(nodeConnector))) {
+            edge = edgeMap.get(nodeConnector);
+            edgeMap.remove(nodeConnector);
+        }
+
+        // notify Topology
+        if (this.discoveryService != null) {
+            this.discoveryService.notifyEdge(edge, UpdateType.REMOVED, null);
+        }
+        logger.trace("Remove {}", nodeConnector);
+    }
+
+    private void removeEdge(NodeConnector nodeConnector) {
+        removeEdge(nodeConnector, isEnabled(nodeConnector));
+    }
+
+    private void updateEdge(Edge edge, UpdateType type, Set<Property> props) {
+        if (discoveryService == null) {
+            return;
+        }
+
+        this.discoveryService.notifyEdge(edge, type, props);
+
+        NodeConnector src = edge.getTailNodeConnector(), dst = edge
+                .getHeadNodeConnector();
+        if (!src.getType().equals(
+                NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+            if (type == UpdateType.ADDED) {
+                edgeMap.put(src, edge);
+            } else {
+                edgeMap.remove(src);
+            }
+        } else {
+            /*
+             * Save Production edge into different DB keyed by the Edge port
+             */
+            if (type == UpdateType.ADDED) {
+                prodMap.put(dst, edge);
+            } else {
+                prodMap.remove(dst);
+            }
+        }
+    }
+
+    private void moreToReadyListHi(NodeConnector nodeConnector) {
+        if (readyListLo.contains(nodeConnector)) {
+            readyListLo.remove(nodeConnector);
+            readyListHi.add(nodeConnector);
+        } else if (waitingList.contains(nodeConnector)) {
+            waitingList.remove(nodeConnector);
+            readyListHi.add(nodeConnector);
+        }
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    private int getDiscoveryConsistencyCheckInterval() {
+        return discoveryConsistencyCheckMultiple * discoveryBatchRestartTicks;
+    }
+
+    private int getDiscoveryFinalTimeoutInterval() {
+        return (discoveryRetry + 1) * discoveryTimeoutTicks;
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---Topology Discovery---\n");
+        help.append("\t prlh                   - Print readyListHi entries\n");
+        help.append("\t prll                   - Print readyListLo entries\n");
+        help.append("\t pwl                    - Print waitingList entries\n");
+        help.append("\t ppl                    - Print pendingList entries\n");
+        help.append("\t ptick                  - Print tick time in msec\n");
+        help.append("\t pcc                    - Print CC info\n");
+        help
+                .append("\t psize                  - Print sizes of all the lists\n");
+        help.append("\t ptm                    - Print timeout info\n");
+        help.append("\t ecc                       - Enable CC\n");
+        help.append("\t dcc                       - Disable CC\n");
+        help
+                .append("\t scc [multiple]         - Set/show CC multiple and interval\n");
+        help.append("\t sports [ports]                    - Set/show max ports per batch\n");
+        help.append("\t spause [ticks]         - Set/show pause ticks\n");
+        help
+                .append("\t sdi [ticks]           - Set/show discovery interval in ticks\n");
+        help.append("\t stm [ticks]            - Set/show per timeout ticks\n");
+        help.append("\t sretry [count]                    - Set/show num of retries\n");
+        help.append("\t addsw <swid>              - Add a switch\n");
+        help.append("\t remsw <swid>              - Remove a switch\n");
+        help.append("\t page                   - Print aging info\n");
+        help.append("\t sage                   - Set/Show aging time limit\n");
+        help.append("\t eage                      - Enable aging\n");
+        help.append("\t dage                      - Disable aging\n");
+        help.append("\t pthrot                 - Print throttling\n");
+        help.append("\t ethrot                    - Enable throttling\n");
+        help.append("\t dthrot                 - Disable throttling\n");
+        return help.toString();
+    }
+
+    public void _prlh(CommandInterpreter ci) {
+        ci.println("ReadyListHi\n");
+        for (NodeConnector nodeConnector : readyListHi) {
+            if (nodeConnector == null) {
+                continue;
+            }
+            ci.println(nodeConnector);
+        }
+    }
+
+    public void _prll(CommandInterpreter ci) {
+        ci.println("ReadyListLo\n");
+        for (NodeConnector nodeConnector : readyListLo) {
+            if (nodeConnector == null) {
+                continue;
+            }
+            ci.println(nodeConnector);
+        }
+    }
+
+    public void _pwl(CommandInterpreter ci) {
+        ci.println("WaitingList\n");
+        for (NodeConnector nodeConnector : waitingList) {
+            if (nodeConnector == null) {
+                continue;
+            }
+            ci.println(nodeConnector);
+        }
+    }
+
+    public void _ppl(CommandInterpreter ci) {
+        ci.println("PendingList\n");
+        for (NodeConnector nodeConnector : pendingMap.keySet()) {
+            if (nodeConnector == null) {
+                continue;
+            }
+            ci.println(nodeConnector);
+        }
+    }
+
+    public void _ptick(CommandInterpreter ci) {
+        ci.println("Current timer is " + discoveryTimerTick + " msec per tick");
+    }
+
+    public void _pcc(CommandInterpreter ci) {
+        if (discoveryConsistencyCheckEnabled) {
+            ci.println("ConsistencyChecker is currently enabled");
+        } else {
+            ci.println("ConsistencyChecker is currently disabled");
+        }
+        ci.println("Interval " + getDiscoveryConsistencyCheckInterval());
+        ci.println("Multiple " + discoveryConsistencyCheckMultiple);
+        ci.println("Number of times called "
+                + discoveryConsistencyCheckCallingTimes);
+        ci.println("Corrected count " + discoveryConsistencyCheckCorrected);
+    }
+
+    public void _ptm(CommandInterpreter ci) {
+        ci.println("Final timeout ticks " + getDiscoveryFinalTimeoutInterval());
+        ci.println("Per timeout ticks " + discoveryTimeoutTicks);
+        ci.println("Retry after initial timeout " + discoveryRetry);
+    }
+
+    public void _psize(CommandInterpreter ci) {
+        ci.println("readyListLo size " + readyListLo.size() + "\n"
+                + "readyListHi size " + readyListHi.size() + "\n"
+                + "waitingList size " + waitingList.size() + "\n"
+                + "pendingMap size " + pendingMap.size() + "\n"
+                + "edgeMap size " + edgeMap.size() + "\n" + "prodMap size "
+                + prodMap.size() + "\n" + "agingMap size " + agingMap.size());
+    }
+
+    public void _page(CommandInterpreter ci) {
+        if (this.discoveryAgingEnabled) {
+            ci.println("Aging is enabled");
+        } else {
+            ci.println("Aging is disabled");
+        }
+        ci.println("Current aging time limit " + this.discoveryAgeoutTicks);
+        ci.println("\n");
+        ci
+                .println("                                Edge                                                       Aging ");
+        Collection<Edge> prodSet = prodMap.values();
+        if (prodSet == null) {
+            return;
+        }
+        for (Edge edge : prodSet) {
+            Integer aging = agingMap.get(edge.getHeadNodeConnector());
+            if (aging != null) {
+                ci.println(edge + "      " + aging);
+            }
+        }
+        ci.println("\n");
+        ci.println("              NodeConnector                                                                        Edge ");
+        Set<NodeConnector> keySet = prodMap.keySet();
+        if (keySet == null) {
+            return;
+        }
+        for (NodeConnector nc : keySet) {
+            ci.println(nc + "      " + prodMap.get(nc));
+        }
+        return;
+    }
+
+    public void _sage(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        if (val == null) {
+            ci.println("Please enter aging time limit. Current value "
+                    + this.discoveryAgeoutTicks);
+            return;
+        }
+        try {
+            this.discoveryAgeoutTicks = Integer.parseInt(val);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _eage(CommandInterpreter ci) {
+        this.discoveryAgingEnabled = true;
+        ci.println("Aging is enabled");
+        return;
+    }
+
+    public void _dage(CommandInterpreter ci) {
+        this.discoveryAgingEnabled = false;
+        ci.println("Aging is disabled");
+        return;
+    }
+
+    public void _scc(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        if (val == null) {
+            ci.println("Please enter CC multiple. Current multiple "
+                    + discoveryConsistencyCheckMultiple + " (interval "
+                    + getDiscoveryConsistencyCheckInterval()
+                    + ") calling times "
+                    + discoveryConsistencyCheckCallingTimes);
+            return;
+        }
+        try {
+            discoveryConsistencyCheckMultiple = Integer.parseInt(val);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _ecc(CommandInterpreter ci) {
+        this.discoveryConsistencyCheckEnabled = true;
+        ci.println("ConsistencyChecker is enabled");
+        return;
+    }
+
+    public void _dcc(CommandInterpreter ci) {
+        this.discoveryConsistencyCheckEnabled = false;
+        ci.println("ConsistencyChecker is disabled");
+        return;
+    }
+
+    public void _pspf(CommandInterpreter ci) {
+        if (this.discoverySpoofingEnabled) {
+            ci.println("Discovery spoofing is enabled");
+        } else {
+            ci.println("Discovery spoofing is disabled");
+        }
+        return;
+    }
+
+    public void _espf(CommandInterpreter ci) {
+        this.discoverySpoofingEnabled = true;
+        ci.println("Discovery spoofing is enabled");
+        return;
+    }
+
+    public void _dspf(CommandInterpreter ci) {
+        this.discoverySpoofingEnabled = false;
+        ci.println("Discovery spoofing is disabled");
+        return;
+    }
+
+    public void _spause(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        String out = "Please enter pause tick value less than "
+                + discoveryBatchRestartTicks + ". Current value is "
+                + discoveryBatchPauseTicks;
+
+        if (val != null) {
+            try {
+                int pause = Integer.parseInt(val);
+                if (pause < discoveryBatchRestartTicks) {
+                    discoveryBatchPauseTicks = pause;
+                    return;
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        ci.println(out);
+    }
+
+    public void _sdi(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        if (val == null) {
+            ci
+                    .println("Please enter discovery interval in ticks. Current value is "
+                            + discoveryBatchRestartTicks);
+            return;
+        }
+        try {
+            discoveryBatchRestartTicks = Integer.parseInt(val);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _sports(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        if (val == null) {
+            ci.println("Please enter max ports per batch. Current value is "
+                    + discoveryBatchMaxPorts);
+            return;
+        }
+        try {
+            discoveryBatchMaxPorts = Integer.parseInt(val);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _sretry(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        if (val == null) {
+            ci.println("Please enter number of retries. Current value is "
+                    + discoveryRetry);
+            return;
+        }
+        try {
+            discoveryRetry = Integer.parseInt(val);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _stm(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        String out = "Please enter timeout tick value less than "
+                + discoveryBatchRestartTicks + ". Current value is "
+                + discoveryTimeoutTicks;
+        if (val != null) {
+            try {
+                int timeout = Integer.parseInt(val);
+                if (timeout < discoveryBatchRestartTicks) {
+                    discoveryTimeoutTicks = timeout;
+                    return;
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        ci.println(out);
+    }
+
+    public void _addsw(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        Long sid;
+        try {
+            sid = Long.parseLong(val);
+            Node node = NodeCreator.createOFNode(sid);
+            addDiscovery(node);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _remsw(CommandInterpreter ci) {
+        String val = ci.nextArgument();
+        Long sid;
+        try {
+            sid = Long.parseLong(val);
+            Node node = NodeCreator.createOFNode(sid);
+            removeDiscovery(node);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    public void _pthrot(CommandInterpreter ci) {
+        if (this.throttling) {
+            ci.println("Throttling is enabled");
+        } else {
+            ci.println("Throttling is disabled");
+        }
+    }
+
+    public void _ethrot(CommandInterpreter ci) {
+        this.throttling = true;
+        ci.println("Throttling is enabled");
+        return;
+    }
+
+    public void _dthrot(CommandInterpreter ci) {
+        this.throttling = false;
+        ci.println("Throttling is disabled");
+        return;
+    }
+
+    @Override
+    public void updateNode(Node node, UpdateType type, Set<Property> props) {
+        switch (type) {
+        case ADDED:
+            addNode(node, props);
+            break;
+        case REMOVED:
+            removeNode(node);
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void updateNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props) {
+        Config config = null;
+        State state = null;
+        boolean enabled = false;
+
+        for (Property prop : props) {
+            if (prop.getName().equals(Config.ConfigPropName)) {
+                config = (Config) prop;
+            } else if (prop.getName().equals(State.StatePropName)) {
+                state = (State) prop;
+            }
+        }
+        enabled = ((config != null) && (config.getValue() == Config.ADMIN_UP)
+                && (state != null) && (state.getValue() == State.EDGE_UP));
+
+        switch (type) {
+        case ADDED:
+            if (enabled) {
+                addDiscovery(nodeConnector);
+                logger.trace("ADDED enabled {}", nodeConnector);
+            } else {
+                logger.trace("ADDED disabled {}", nodeConnector);
+            }
+            break;
+        case CHANGED:
+            if (enabled) {
+                addDiscovery(nodeConnector);
+                logger.trace("CHANGED enabled {}", nodeConnector);
+            } else {
+                removeDiscovery(nodeConnector);
+                logger.trace("CHANGED disabled {}", nodeConnector);
+            }
+            break;
+        case REMOVED:
+            removeDiscovery(nodeConnector);
+            logger.trace("REMOVED enabled {}", nodeConnector);
+            break;
+        default:
+            return;
+        }
+    }
+
+    public void addNode(Node node, Set<Property> props) {
+        if (node == null)
+            return;
+
+        addDiscovery(node);
+    }
+
+    public void removeNode(Node node) {
+        if (node == null)
+            return;
+
+        removeDiscovery(node);
+    }
+
+    public void updateNode(Node node, Set<Property> props) {
+    }
+
+    void setController(IController s) {
+        this.controller = s;
+    }
+
+    void unsetController(IController s) {
+        if (this.controller == s) {
+            this.controller = null;
+        }
+    }
+
+    public void setPluginInInventoryService(IPluginInInventoryService service) {
+        this.pluginInInventoryService = service;
+    }
+
+    public void unsetPluginInInventoryService(IPluginInInventoryService service) {
+        this.pluginInInventoryService = null;
+    }
+
+    public void setIDataPacketMux(IDataPacketMux service) {
+        this.iDataPacketMux = service;
+    }
+
+    public void unsetIDataPacketMux(IDataPacketMux service) {
+        if (this.iDataPacketMux == service) {
+            this.iDataPacketMux = null;
+        }
+    }
+
+    void setDiscoveryService(IDiscoveryService s) {
+        this.discoveryService = s;
+    }
+
+    void unsetDiscoveryService(IDiscoveryService s) {
+        if (this.discoveryService == s) {
+            this.discoveryService = null;
+        }
+    }
+
+    private void initDiscoveryPacket() {
+        // Create LLDP ChassisID TLV
+        chassisIdTlv = new LLDPTLV();
+        chassisIdTlv.setType((byte) LLDPTLV.TLVType.ChassisID.getValue());
+
+        // Create LLDP PortID TLV
+        portIdTlv = new LLDPTLV();
+        portIdTlv.setType((byte) LLDPTLV.TLVType.PortID.getValue());
+
+        // Create LLDP TTL TLV
+        byte[] ttl = new byte[] { (byte) 120 };
+        ttlTlv = new LLDPTLV();
+        ttlTlv.setType((byte) LLDPTLV.TLVType.TTL.getValue()).setLength(
+                (short) ttl.length).setValue(ttl);
+
+        customTlv = new LLDPTLV();
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        logger.trace("Init called");
+
+        transmitQ = new LinkedBlockingQueue<NodeConnector>();
+
+        readyListHi = new CopyOnWriteArrayList<NodeConnector>();
+        readyListLo = new CopyOnWriteArrayList<NodeConnector>();
+        waitingList = new CopyOnWriteArrayList<NodeConnector>();
+        pendingMap = new ConcurrentHashMap<NodeConnector, Integer>();
+        edgeMap = new ConcurrentHashMap<NodeConnector, Edge>();
+        agingMap = new ConcurrentHashMap<NodeConnector, Integer>();
+        prodMap = new ConcurrentHashMap<NodeConnector, Edge>();
+
+        discoveryTimer = new Timer("DiscoveryService");
+        discoveryTimerTask = new DiscoveryTimerTask();
+
+        transmitThread = new Thread(new DiscoveryTransmit(transmitQ));
+
+        initDiscoveryPacket();
+
+        registerWithOSGIConsole();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one dependency
+     * become unsatisfied or when the component is shutting down because for
+     * example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        transmitQ = null;
+        readyListHi = null;
+        readyListLo = null;
+        waitingList = null;
+        pendingMap = null;
+        edgeMap = null;
+        agingMap = null;
+        prodMap = null;
+        discoveryTimer = null;
+        discoveryTimerTask = null;
+        transmitThread = null;
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called and after
+     * the services provided by the class are registered in the service registry
+     *
+     */
+    void start() {
+        discoveryTimer.schedule(discoveryTimerTask, discoveryTimerTick,
+                discoveryTimerTick);
+        transmitThread.start();
+    }
+
+    /**
+     * Function called after registering the
+     * service in OSGi service registry.
+     */
+    void started() {
+        /* get a snapshot of all the existing switches */
+        addDiscovery();
+    }
+
+    /**
+     * Function called by the dependency manager before the services exported by
+     * the component are unregistered, this will be followed by a "destroy ()"
+     * calls
+     *
+     */
+    void stop() {
+        shuttingDown = true;
+        discoveryTimer.cancel();
+        transmitThread.interrupt();
+    }
+
+    @Override
+    public void tagUpdated(String containerName, Node n, short oldTag,
+            short newTag, UpdateType t) {
+    }
+
+    @Override
+    public void containerFlowUpdated(String containerName,
+            ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) {
+    }
+
+    @Override
+    public void nodeConnectorUpdated(String containerName, NodeConnector p,
+            UpdateType t) {
+        switch (t) {
+        case ADDED:
+            moreToReadyListHi(p);
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void containerModeUpdated(UpdateType t) {
+        // do nothing
+    }
+    
+    private byte[] getSouceMACFromNodeID(String nodeId) {        
+        byte[] cid = HexEncode.bytesFromHexString(nodeId);
+        byte[] sourceMac = new byte[6];
+        int pos = cid.length - sourceMac.length;
+
+        if (pos >= 0) {
+               System.arraycopy(cid, pos, sourceMac, 0, sourceMac.length);
+        }
+        
+        return sourceMac;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowConverter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowConverter.java
new file mode 100644 (file)
index 0000000..407535f
--- /dev/null
@@ -0,0 +1,699 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6FlowMod;
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6Match;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionDataLayerDestination;
+import org.openflow.protocol.action.OFActionDataLayerSource;
+import org.openflow.protocol.action.OFActionNetworkLayerAddress;
+import org.openflow.protocol.action.OFActionNetworkLayerDestination;
+import org.openflow.protocol.action.OFActionNetworkLayerSource;
+import org.openflow.protocol.action.OFActionNetworkTypeOfService;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.action.OFActionStripVirtualLan;
+import org.openflow.protocol.action.OFActionTransportLayer;
+import org.openflow.protocol.action.OFActionTransportLayerDestination;
+import org.openflow.protocol.action.OFActionTransportLayerSource;
+import org.openflow.protocol.action.OFActionVirtualLanIdentifier;
+import org.openflow.protocol.action.OFActionVirtualLanPriorityCodePoint;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.ActionType;
+import org.opendaylight.controller.sal.action.Controller;
+import org.opendaylight.controller.sal.action.Drop;
+import org.opendaylight.controller.sal.action.Flood;
+import org.opendaylight.controller.sal.action.FloodAll;
+import org.opendaylight.controller.sal.action.HwPath;
+import org.opendaylight.controller.sal.action.Loopback;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.PopVlan;
+import org.opendaylight.controller.sal.action.SetDlDst;
+import org.opendaylight.controller.sal.action.SetDlSrc;
+import org.opendaylight.controller.sal.action.SetNwDst;
+import org.opendaylight.controller.sal.action.SetNwSrc;
+import org.opendaylight.controller.sal.action.SetNwTos;
+import org.opendaylight.controller.sal.action.SetTpDst;
+import org.opendaylight.controller.sal.action.SetTpSrc;
+import org.opendaylight.controller.sal.action.SetVlanId;
+import org.opendaylight.controller.sal.action.SetVlanPcp;
+import org.opendaylight.controller.sal.action.SwPath;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchField;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.utils.NetUtils;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+
+/**
+ * Utility class for converting a SAL Flow into the OF flow and vice-versa
+ *
+ *
+ *
+ */
+public class FlowConverter {
+    private Flow flow; // SAL Flow
+    private OFMatch ofMatch; // OF 1.0 match or OF 1.0 + IPv6 extension match
+    private List<OFAction> actionsList; // OF 1.0 actions
+    private int actionsLength;
+    private boolean isIPv6;
+
+    public FlowConverter(OFMatch ofMatch, List<OFAction> actionsList) {
+        this.ofMatch = ofMatch;
+        this.actionsList = actionsList;
+        this.actionsLength = 0;
+        this.flow = null;
+        this.isIPv6 = ofMatch instanceof V6Match;
+    }
+
+    public FlowConverter(Flow flow) {
+        this.ofMatch = null;
+        this.actionsList = null;
+        this.actionsLength = 0;
+        this.flow = flow;
+        this.isIPv6 = flow.isIPv6();
+    }
+
+    /**
+     * Returns the match in OF 1.0 (OFMatch) form or OF 1.0 + IPv6 extensions form (V6Match)
+     *
+     * @return
+     */
+    public OFMatch getOFMatch() {
+        if (ofMatch == null) {
+            Match match = flow.getMatch();
+            ofMatch = (isIPv6) ? new V6Match() : new OFMatch();
+
+            int wildcards = OFMatch.OFPFW_ALL;
+            if (match.isPresent(MatchType.IN_PORT)) {
+                short port = (Short) ((NodeConnector) match.getField(
+                        MatchType.IN_PORT).getValue()).getID();
+                if (!isIPv6) {
+                    ofMatch.setInputPort(port);
+                    wildcards &= ~OFMatch.OFPFW_IN_PORT;
+                } else {
+                    ((V6Match) ofMatch).setInputPort(port, (short) 0);
+                }
+            }
+            if (match.isPresent(MatchType.DL_SRC)) {
+                byte[] srcMac = (byte[]) match.getField(MatchType.DL_SRC)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setDataLayerSource(srcMac.clone());
+                    wildcards &= ~OFMatch.OFPFW_DL_SRC;
+                } else {
+                    ((V6Match) ofMatch).setDataLayerSource(srcMac, null);
+                }
+            }
+            if (match.isPresent(MatchType.DL_DST)) {
+                byte[] dstMac = (byte[]) match.getField(MatchType.DL_DST)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setDataLayerDestination(dstMac.clone());
+                    wildcards &= ~OFMatch.OFPFW_DL_DST;
+                } else {
+                    ((V6Match) ofMatch).setDataLayerDestination(dstMac, null);
+                }
+            }
+            if (match.isPresent(MatchType.DL_VLAN)) {
+                short vlan = (Short) match.getField(MatchType.DL_VLAN)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setDataLayerVirtualLan(vlan);
+                    wildcards &= ~OFMatch.OFPFW_DL_VLAN;
+                } else {
+                    ((V6Match) ofMatch).setDataLayerVirtualLan(vlan, (short) 0);
+                }
+            }
+            if (match.isPresent(MatchType.DL_VLAN_PR)) {
+                byte vlanPr = (Byte) match.getField(MatchType.DL_VLAN_PR)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setDataLayerVirtualLanPriorityCodePoint(vlanPr);
+                    wildcards &= ~OFMatch.OFPFW_DL_VLAN_PCP;
+                } else {
+                    ((V6Match) ofMatch)
+                            .setDataLayerVirtualLanPriorityCodePoint(vlanPr,
+                                    (byte) 0);
+                }
+            }
+            if (match.isPresent(MatchType.DL_TYPE)) {
+                short ethType = (Short) match.getField(MatchType.DL_TYPE)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setDataLayerType(ethType);
+                    wildcards &= ~OFMatch.OFPFW_DL_TYPE;
+                } else {
+                    ((V6Match) ofMatch).setDataLayerType(ethType, (short) 0);
+                }
+            }
+            if (match.isPresent(MatchType.NW_TOS)) {
+                byte tos = (Byte) match.getField(MatchType.NW_TOS).getValue();
+                if (!isIPv6) {
+                    ofMatch.setNetworkTypeOfService(tos);
+                    wildcards &= ~OFMatch.OFPFW_NW_TOS;
+                } else {
+                    ((V6Match) ofMatch).setNetworkTypeOfService(tos, (byte) 0);
+                }
+            }
+            if (match.isPresent(MatchType.NW_PROTO)) {
+                byte proto = (Byte) match.getField(MatchType.NW_PROTO)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setNetworkProtocol(proto);
+                    wildcards &= ~OFMatch.OFPFW_NW_PROTO;
+                } else {
+                    ((V6Match) ofMatch).setNetworkProtocol(proto, (byte) 0);
+                }
+            }
+            if (match.isPresent(MatchType.NW_SRC)) {
+                InetAddress address = (InetAddress) match.getField(
+                        MatchType.NW_SRC).getValue();
+                InetAddress mask = (InetAddress) match.getField(
+                        MatchType.NW_SRC).getMask();
+                if (!isIPv6) {
+                    ofMatch.setNetworkSource(NetUtils.byteArray4ToInt(address
+                            .getAddress()));
+                    int maskLength = NetUtils
+                            .getSubnetMaskLength((mask == null) ? null : mask
+                                    .getAddress());
+                    wildcards = (wildcards & ~OFMatch.OFPFW_NW_SRC_MASK)
+                            | (maskLength << OFMatch.OFPFW_NW_SRC_SHIFT);
+                } else {
+                    ((V6Match) ofMatch).setNetworkSource(address, mask);
+                }
+            }
+            if (match.isPresent(MatchType.NW_DST)) {
+                InetAddress address = (InetAddress) match.getField(
+                        MatchType.NW_DST).getValue();
+                InetAddress mask = (InetAddress) match.getField(
+                        MatchType.NW_DST).getMask();
+                if (!isIPv6) {
+                    ofMatch.setNetworkDestination(NetUtils
+                            .byteArray4ToInt(address.getAddress()));
+                    int maskLength = NetUtils
+                            .getSubnetMaskLength((mask == null) ? null : mask
+                                    .getAddress());
+                    wildcards = (wildcards & ~OFMatch.OFPFW_NW_DST_MASK)
+                            | (maskLength << OFMatch.OFPFW_NW_DST_SHIFT);
+                } else {
+                    ((V6Match) ofMatch).setNetworkDestination(address, mask);
+                }
+            }
+            if (match.isPresent(MatchType.TP_SRC)) {
+                short port = (Short) match.getField(MatchType.TP_SRC)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setTransportSource(port);
+                    wildcards &= ~OFMatch.OFPFW_TP_SRC;
+                } else {
+                    ((V6Match) ofMatch).setTransportSource(port, (short) 0);
+                }
+            }
+            if (match.isPresent(MatchType.TP_DST)) {
+                short port = (Short) match.getField(MatchType.TP_DST)
+                        .getValue();
+                if (!isIPv6) {
+                    ofMatch.setTransportDestination(port);
+                    wildcards &= ~OFMatch.OFPFW_TP_DST;
+                } else {
+                    ((V6Match) ofMatch)
+                            .setTransportDestination(port, (short) 0);
+                }
+            }
+
+            if (!isIPv6) {
+                ofMatch.setWildcards(U32.t(Long.valueOf(wildcards)));
+            }
+        }
+
+        return ofMatch;
+    }
+
+    /**
+     * Returns the list of actions in OF 1.0 form
+     * @return
+     */
+    public List<OFAction> getOFActions() {
+        if (this.actionsList == null) {
+            actionsList = new ArrayList<OFAction>();
+            for (Action action : flow.getActions()) {
+                if (action.getType() == ActionType.OUTPUT) {
+                    Output a = (Output) action;
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setMaxLength((short) 0xffff);
+                    ofAction.setPort(PortConverter.toOFPort(a.getPort()));
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.DROP) {
+                    continue;
+                }
+                if (action.getType() == ActionType.LOOPBACK) {
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setPort(OFPort.OFPP_IN_PORT.getValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.FLOOD) {
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setPort(OFPort.OFPP_FLOOD.getValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.FLOOD_ALL) {
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setPort(OFPort.OFPP_ALL.getValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.CONTROLLER) {
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setPort(OFPort.OFPP_CONTROLLER.getValue());
+                    // We want the whole frame hitting the match be sent to the controller
+                    ofAction.setMaxLength((short) 0xffff);
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SW_PATH) {
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setPort(OFPort.OFPP_LOCAL.getValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.HW_PATH) {
+                    OFActionOutput ofAction = new OFActionOutput();
+                    ofAction.setPort(OFPort.OFPP_NORMAL.getValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionOutput.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_VLAN_ID) {
+                    SetVlanId a = (SetVlanId) action;
+                    OFActionVirtualLanIdentifier ofAction = new OFActionVirtualLanIdentifier();
+                    ofAction.setVirtualLanIdentifier((short) a.getVlanId());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionVirtualLanIdentifier.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_VLAN_PCP) {
+                    SetVlanPcp a = (SetVlanPcp) action;
+                    OFActionVirtualLanPriorityCodePoint ofAction = new OFActionVirtualLanPriorityCodePoint();
+                    ofAction.setVirtualLanPriorityCodePoint(Integer.valueOf(
+                            a.getPcp()).byteValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionVirtualLanPriorityCodePoint.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.POP_VLAN) {
+                    OFActionStripVirtualLan ofAction = new OFActionStripVirtualLan();
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionStripVirtualLan.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_DL_SRC) {
+                    SetDlSrc a = (SetDlSrc) action;
+                    OFActionDataLayerSource ofAction = new OFActionDataLayerSource();
+                    ofAction.setDataLayerAddress(a.getDlAddress());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionDataLayerSource.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_DL_DST) {
+                    SetDlDst a = (SetDlDst) action;
+                    OFActionDataLayerDestination ofAction = new OFActionDataLayerDestination();
+                    ofAction.setDataLayerAddress(a.getDlAddress());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionDataLayerDestination.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_NW_SRC) {
+                    SetNwSrc a = (SetNwSrc) action;
+                    OFActionNetworkLayerSource ofAction = new OFActionNetworkLayerSource();
+                    ofAction.setNetworkAddress(NetUtils.byteArray4ToInt(a
+                            .getAddress().getAddress()));
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionNetworkLayerAddress.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_NW_DST) {
+                    SetNwDst a = (SetNwDst) action;
+                    OFActionNetworkLayerDestination ofAction = new OFActionNetworkLayerDestination();
+                    ofAction.setNetworkAddress(NetUtils.byteArray4ToInt(a
+                            .getAddress().getAddress()));
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionNetworkLayerAddress.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_NW_TOS) {
+                    SetNwTos a = (SetNwTos) action;
+                    OFActionNetworkTypeOfService ofAction = new OFActionNetworkTypeOfService();
+                    ofAction.setNetworkTypeOfService(Integer.valueOf(
+                            a.getNwTos()).byteValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionNetworkTypeOfService.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_TP_SRC) {
+                    SetTpSrc a = (SetTpSrc) action;
+                    OFActionTransportLayerSource ofAction = new OFActionTransportLayerSource();
+                    ofAction.setTransportPort(Integer.valueOf(a.getPort())
+                            .shortValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionTransportLayer.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_TP_DST) {
+                    SetTpDst a = (SetTpDst) action;
+                    OFActionTransportLayerDestination ofAction = new OFActionTransportLayerDestination();
+                    ofAction.setTransportPort(Integer.valueOf(a.getPort())
+                            .shortValue());
+                    actionsList.add(ofAction);
+                    actionsLength += OFActionTransportLayer.MINIMUM_LENGTH;
+                    continue;
+                }
+                if (action.getType() == ActionType.SET_NEXT_HOP) {
+                    //TODO
+                    continue;
+                }
+            }
+        }
+        return actionsList;
+    }
+
+    /**
+     * Utility to convert a SAL flow to an OF 1.0 (OFFlowMod) or
+     * to an OF 1.0 + IPv6 extension (V6FlowMod) Flow modifier Message
+     *
+     * @param sw
+     * @param command
+     * @param port
+     * @return
+     */
+    public OFMessage getOFFlowMod(/*ISwitch sw, */short command, OFPort port) {
+        OFMessage fm = (isIPv6) ? new V6FlowMod() : new OFFlowMod();
+        if (this.ofMatch == null) {
+            getOFMatch();
+        }
+        if (this.actionsList == null) {
+            getOFActions();
+        }
+        if (!isIPv6) {
+            ((OFFlowMod) fm).setMatch(this.ofMatch);
+            ((OFFlowMod) fm).setActions(this.actionsList);
+            ((OFFlowMod) fm).setPriority(flow.getPriority());
+            ((OFFlowMod) fm).setCookie(flow.getId());
+            ((OFFlowMod) fm).setBufferId(OFPacketOut.BUFFER_ID_NONE);
+            ((OFFlowMod) fm).setLength(U16.t(OFFlowMod.MINIMUM_LENGTH
+                    + actionsLength));
+            ((OFFlowMod) fm).setIdleTimeout(flow.getIdleTimeout());
+            ((OFFlowMod) fm).setHardTimeout(flow.getHardTimeout());
+            ((OFFlowMod) fm).setCommand(command);
+            if (port != null) {
+                ((OFFlowMod) fm).setOutPort(port);
+            }
+        } else {
+            ((V6FlowMod) fm).setVendor();
+            ((V6FlowMod) fm).setMatch((V6Match) ofMatch);
+            ((V6FlowMod) fm).setActions(this.actionsList);
+            ((V6FlowMod) fm).setPriority(flow.getPriority());
+            ((V6FlowMod) fm).setCookie(flow.getId());
+            ((V6FlowMod) fm).setLength(U16.t(OFVendor.MINIMUM_LENGTH
+                    + ((V6Match) ofMatch).getIPv6ExtMinHdrLen()
+                    + ((V6Match) ofMatch).getIPv6MatchLen()
+                    + ((V6Match) ofMatch).getPadSize() + actionsLength));
+            ((V6FlowMod) fm).setIdleTimeout(flow.getIdleTimeout());
+            ((V6FlowMod) fm).setHardTimeout(flow.getHardTimeout());
+            ((V6FlowMod) fm).setCommand(command);
+            if (port != null) {
+                ((V6FlowMod) fm).setOutPort(port);
+            }
+        }
+        return fm;
+    }
+
+    public Flow getFlow(Node node) {
+        if (this.flow == null) {
+            Match salMatch = new Match();
+
+            /*
+             * Installed flow may not have a Match defined
+             * like in case of a drop all flow
+             */
+            if (ofMatch != null) {
+                if (!isIPv6) {
+                    // Compute OF1.0 Match
+                    if (ofMatch.getInputPort() != 0) {
+                        salMatch.setField(new MatchField(MatchType.IN_PORT,
+                                NodeConnectorCreator.createNodeConnector(
+                                        (Short) ofMatch.getInputPort(), node)));
+                    }
+                    if (ofMatch.getDataLayerSource() != null
+                            && !NetUtils
+                                    .isZeroMAC(ofMatch.getDataLayerSource())) {
+                        byte srcMac[] = ofMatch.getDataLayerSource();
+                        salMatch.setField(new MatchField(MatchType.DL_SRC,
+                                srcMac.clone()));
+                    }
+                    if (ofMatch.getDataLayerDestination() != null
+                            && !NetUtils.isZeroMAC(ofMatch
+                                    .getDataLayerDestination())) {
+                        byte dstMac[] = ofMatch.getDataLayerDestination();
+                        salMatch.setField(new MatchField(MatchType.DL_DST,
+                                dstMac.clone()));
+                    }
+                    if (ofMatch.getDataLayerType() != 0) {
+                        salMatch.setField(new MatchField(MatchType.DL_TYPE,
+                                ofMatch.getDataLayerType()));
+                    }
+                    if (ofMatch.getDataLayerVirtualLan() != 0) {
+                        salMatch.setField(new MatchField(MatchType.DL_VLAN,
+                                ofMatch.getDataLayerVirtualLan()));
+                    }
+                    if (ofMatch.getDataLayerVirtualLanPriorityCodePoint() != 0) {
+                        salMatch.setField(MatchType.DL_VLAN_PR, ofMatch
+                                .getDataLayerVirtualLanPriorityCodePoint());
+                    }
+                    if (ofMatch.getNetworkSource() != 0) {
+                        salMatch.setField(MatchType.NW_SRC, NetUtils
+                                .getInetAddress(ofMatch.getNetworkSource()),
+                                NetUtils.getInetNetworkMask(ofMatch
+                                        .getNetworkSourceMaskLen(), false));
+                    }
+                    if (ofMatch.getNetworkDestination() != 0) {
+                        salMatch
+                                .setField(
+                                        MatchType.NW_DST,
+                                        NetUtils.getInetAddress(ofMatch
+                                                .getNetworkDestination()),
+                                        NetUtils
+                                                .getInetNetworkMask(
+                                                        ofMatch
+                                                                .getNetworkDestinationMaskLen(),
+                                                        false));
+                    }
+                    if (ofMatch.getNetworkTypeOfService() != 0) {
+                        salMatch.setField(MatchType.NW_TOS, ofMatch
+                                .getNetworkTypeOfService());
+                    }
+                    if (ofMatch.getNetworkProtocol() != 0) {
+                        salMatch.setField(MatchType.NW_PROTO, ofMatch
+                                .getNetworkProtocol());
+                    }
+                    if (ofMatch.getTransportSource() != 0) {
+                        salMatch.setField(MatchType.TP_SRC, ((Short) ofMatch
+                                .getTransportSource()));
+                    }
+                    if (ofMatch.getTransportDestination() != 0) {
+                        salMatch.setField(MatchType.TP_DST, ((Short) ofMatch
+                                .getTransportDestination()));
+                    }
+                } else {
+                    // Compute OF1.0 + IPv6 extensions Match
+                    V6Match v6Match = (V6Match) ofMatch;
+                    if (v6Match.getInputPort() != 0) {
+                        // Mask on input port is not defined
+                        salMatch.setField(new MatchField(MatchType.IN_PORT,
+                                NodeConnectorCreator.createOFNodeConnector(
+                                        (Short) v6Match.getInputPort(), node)));
+                    }
+                    if (v6Match.getDataLayerSource() != null
+                            && !NetUtils
+                                    .isZeroMAC(ofMatch.getDataLayerSource())) {
+                        byte srcMac[] = v6Match.getDataLayerSource();
+                        salMatch.setField(new MatchField(MatchType.DL_SRC,
+                                srcMac.clone()));
+                    }
+                    if (v6Match.getDataLayerDestination() != null
+                            && !NetUtils.isZeroMAC(ofMatch
+                                    .getDataLayerDestination())) {
+                        byte dstMac[] = v6Match.getDataLayerDestination();
+                        salMatch.setField(new MatchField(MatchType.DL_DST,
+                                dstMac.clone()));
+                    }
+                    if (v6Match.getDataLayerType() != 0) {
+                        salMatch.setField(new MatchField(MatchType.DL_TYPE,
+                                v6Match.getDataLayerType()));
+                    }
+                    if (v6Match.getDataLayerVirtualLan() != 0) {
+                        salMatch.setField(new MatchField(MatchType.DL_VLAN,
+                                v6Match.getDataLayerVirtualLan()));
+                    }
+                    if (v6Match.getDataLayerVirtualLanPriorityCodePoint() != 0) {
+                        salMatch.setField(MatchType.DL_VLAN_PR, v6Match
+                                .getDataLayerVirtualLanPriorityCodePoint());
+                    }
+                    if (v6Match.getNetworkSrc() != null) {
+                        salMatch.setField(MatchType.NW_SRC, v6Match
+                                .getNetworkSrc(), v6Match
+                                .getNetworkSourceMask());
+                    }
+                    if (v6Match.getNetworkDest() != null) {
+                        salMatch.setField(MatchType.NW_DST, v6Match
+                                .getNetworkDest(), v6Match
+                                .getNetworkDestinationMask());
+                    }
+                    if (v6Match.getNetworkTypeOfService() != 0) {
+                        salMatch.setField(MatchType.NW_TOS, v6Match
+                                .getNetworkTypeOfService());
+                    }
+                    if (v6Match.getNetworkProtocol() != 0) {
+                        salMatch.setField(MatchType.NW_PROTO, v6Match
+                                .getNetworkProtocol());
+                    }
+                    if (v6Match.getTransportSource() != 0) {
+                        salMatch.setField(MatchType.TP_SRC, ((Short) v6Match
+                                .getTransportSource()));
+                    }
+                    if (v6Match.getTransportDestination() != 0) {
+                        salMatch.setField(MatchType.TP_DST, ((Short) v6Match
+                                .getTransportDestination()));
+                    }
+                }
+            }
+
+            // Convert actions
+            Action salAction = null;
+            List<Action> salActionList = new ArrayList<Action>();
+            if (actionsList == null) {
+                salActionList.add(new Drop());
+            } else {
+                for (OFAction ofAction : actionsList) {
+                    if (ofAction instanceof OFActionOutput) {
+                        short ofPort = ((OFActionOutput) ofAction).getPort();
+                        if (ofPort == OFPort.OFPP_CONTROLLER.getValue()) {
+                            salAction = new Controller();
+                        } else if (ofPort == OFPort.OFPP_NONE.getValue()) {
+                            salAction = new Drop();
+                        } else if (ofPort == OFPort.OFPP_IN_PORT.getValue()) {
+                            salAction = new Loopback();
+                        } else if (ofPort == OFPort.OFPP_FLOOD.getValue()) {
+                            salAction = new Flood();
+                        } else if (ofPort == OFPort.OFPP_ALL.getValue()) {
+                            salAction = new FloodAll();
+                        } else if (ofPort == OFPort.OFPP_LOCAL.getValue()) {
+                            salAction = new SwPath();
+                        } else if (ofPort == OFPort.OFPP_NORMAL.getValue()) {
+                            salAction = new HwPath();
+                        } else if (ofPort == OFPort.OFPP_TABLE.getValue()) {
+                            salAction = new HwPath(); //TODO: we do not handle table in sal for now
+                        } else {
+                            salAction = new Output(NodeConnectorCreator
+                                    .createOFNodeConnector(ofPort, node));
+                        }
+                    } else if (ofAction instanceof OFActionVirtualLanIdentifier) {
+                        salAction = new SetVlanId(
+                                ((OFActionVirtualLanIdentifier) ofAction)
+                                        .getVirtualLanIdentifier());
+                    } else if (ofAction instanceof OFActionStripVirtualLan) {
+                        salAction = new PopVlan();
+                    } else if (ofAction instanceof OFActionVirtualLanPriorityCodePoint) {
+                        salAction = new SetVlanPcp(
+                                ((OFActionVirtualLanPriorityCodePoint) ofAction)
+                                        .getVirtualLanPriorityCodePoint());
+                    } else if (ofAction instanceof OFActionDataLayerSource) {
+                        salAction = new SetDlSrc(
+                                ((OFActionDataLayerSource) ofAction)
+                                        .getDataLayerAddress().clone());
+                    } else if (ofAction instanceof OFActionDataLayerDestination) {
+                        salAction = new SetDlDst(
+                                ((OFActionDataLayerDestination) ofAction)
+                                        .getDataLayerAddress().clone());
+                    } else if (ofAction instanceof OFActionNetworkLayerSource) {
+                        byte addr[] = BigInteger.valueOf(
+                                ((OFActionNetworkLayerSource) ofAction)
+                                        .getNetworkAddress()).toByteArray();
+                        InetAddress ip = null;
+                        try {
+                            ip = InetAddress.getByAddress(addr);
+                        } catch (UnknownHostException e) {
+                            e.printStackTrace();
+                        }
+                        salAction = new SetNwSrc(ip);
+                    } else if (ofAction instanceof OFActionNetworkLayerDestination) {
+                        byte addr[] = BigInteger.valueOf(
+                                ((OFActionNetworkLayerDestination) ofAction)
+                                        .getNetworkAddress()).toByteArray();
+                        InetAddress ip = null;
+                        try {
+                            ip = InetAddress.getByAddress(addr);
+                        } catch (UnknownHostException e) {
+                            e.printStackTrace();
+                        }
+                        salAction = new SetNwDst(ip);
+                    } else if (ofAction instanceof OFActionNetworkTypeOfService) {
+                        salAction = new SetNwTos(
+                                ((OFActionNetworkTypeOfService) ofAction)
+                                        .getNetworkTypeOfService());
+                    } else if (ofAction instanceof OFActionTransportLayerSource) {
+                        Short port = ((OFActionTransportLayerSource) ofAction)
+                                .getTransportPort();
+                        int intPort = (port < 0) ? (port.intValue() & 0x7FFF | 0x8000)
+                                : port;
+                        salAction = new SetTpSrc(intPort);
+                    } else if (ofAction instanceof OFActionTransportLayerDestination) {
+                        Short port = ((OFActionTransportLayerDestination) ofAction)
+                                .getTransportPort();
+                        int intPort = (port < 0) ? (port.intValue() & 0x7FFF | 0x8000)
+                                : port;
+                        salAction = new SetTpDst(intPort);
+                    }
+                    salActionList.add(salAction);
+                }
+            }
+            // Create Flow
+            flow = new Flow(salMatch, salActionList);
+        }
+        return flow;
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerService.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerService.java
new file mode 100644 (file)
index 0000000..b30b5c7
--- /dev/null
@@ -0,0 +1,245 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPort;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Node.NodeIDType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.flowprogrammer.IPluginInFlowProgrammerService;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * Represents the openflow plugin component in charge of programming the flows
+ * on the switch. It servers the install requests coming from the SAL layer.
+ *
+ *
+ *
+ */
+public class FlowProgrammerService implements IPluginInFlowProgrammerService {
+    private IController controller;
+
+    public FlowProgrammerService() {
+        controller = null;
+    }
+
+    public void setController(IController core) {
+        this.controller = core;
+    }
+
+    public void unsetController(IController core) {
+        if (this.controller == core) {
+            this.controller = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    @Override
+    public Status addFlow(Node node, Flow flow) {
+        String action = "add";
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            return new Status(StatusCode.NOTACCEPTABLE,
+                    errorString("send", action, "Invalid node type"));
+        }
+
+        if (controller != null) {
+            ISwitch sw = controller.getSwitch((Long) node.getID());
+            if (sw != null) {
+                FlowConverter x = new FlowConverter(flow);
+                OFMessage msg = x.getOFFlowMod(OFFlowMod.OFPFC_ADD, null);
+
+                /*
+                 * Synchronous message send
+                 */
+                Object result = sw.syncSend(msg);
+                if (result instanceof Boolean) {
+                    return ((Boolean) result == Boolean.TRUE) ?
+                            new Status(StatusCode.SUCCESS, null)
+                            : new Status(StatusCode.TIMEOUT,
+                                    errorString(null, action,
+                                            "Request Timed Out"));
+                } else if (result instanceof OFError) {
+                    return new Status(StatusCode.INTERNALERROR,
+                            errorString("program", action, Utils
+                            .getOFErrorString((OFError) result)));
+                } else {
+                    return new Status(StatusCode.INTERNALERROR,
+                            errorString("send", action, "Internal Error"));
+                }
+            } else {
+                return new Status(StatusCode.GONE, errorString("send", action,
+                                "Switch is not available"));
+            }
+        }
+        return new Status(StatusCode.INTERNALERROR,
+                errorString("send", action, "Internal plugin error"));
+    }
+
+    @Override
+    public Status modifyFlow(Node node, Flow oldFlow, Flow newFlow) {
+        String action = "modify";
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            return new Status(StatusCode.NOTACCEPTABLE,
+                    errorString("send", action, "Invalid node type"));
+        }
+        if (controller != null) {
+            ISwitch sw = controller.getSwitch((Long) node.getID());
+            if (sw != null) {
+                OFMessage msg1 = null, msg2 = null;
+
+                // If priority and match portion are the same, send a modification message
+                if (oldFlow.getPriority() != newFlow.getPriority()
+                        || !oldFlow.getMatch().equals(newFlow.getMatch())) {
+                    msg1 = new FlowConverter(oldFlow).getOFFlowMod(
+                            OFFlowMod.OFPFC_DELETE_STRICT, OFPort.OFPP_NONE);
+                    msg2 = new FlowConverter(newFlow).getOFFlowMod(
+                            OFFlowMod.OFPFC_ADD, null);
+                } else {
+                    msg1 = new FlowConverter(newFlow).getOFFlowMod(
+                            OFFlowMod.OFPFC_MODIFY_STRICT, null);
+                }
+                /*
+                 * Synchronous message send
+                 */
+                action = (msg2 == null) ? "modify" : "delete";
+                Object result = sw.syncSend(msg1);
+                if (result instanceof Boolean) {
+                    if ((Boolean) result == Boolean.FALSE) {
+                        return new Status(StatusCode.TIMEOUT,
+                                errorString(null, action,
+                                        "Request Timed Out"));
+                    } else if (msg2 == null) {
+                        return new Status(StatusCode.SUCCESS, null);
+                    }
+                } else if (result instanceof OFError) {
+                    return new Status(StatusCode.INTERNALERROR,
+                            errorString("program", action, Utils
+                            .getOFErrorString((OFError) result)));
+                } else {
+                    return new Status(StatusCode.INTERNALERROR,
+                            errorString("send", action, "Internal Error"));
+                }
+
+                if (msg2 != null) {
+                    action = "add";
+                    result = sw.syncSend(msg2);
+                    if (result instanceof Boolean) {
+                        return ((Boolean) result == Boolean.TRUE) ?
+                                new Status(StatusCode.SUCCESS, null)
+                                : new Status(StatusCode.TIMEOUT,
+                                        errorString(null, action,
+                                                "Request Timed Out"));
+                    } else if (result instanceof OFError) {
+                        return new Status(StatusCode.INTERNALERROR,
+                                errorString("program", action, Utils
+                                .getOFErrorString((OFError) result)));
+                    } else {
+                        return new Status(StatusCode.INTERNALERROR,
+                                errorString("send", action, "Internal Error"));
+                    }
+                }
+            } else {
+                return new Status(StatusCode.GONE, errorString("send", action,
+                        "Switch is not available"));
+            }
+        }
+        return new Status(StatusCode.INTERNALERROR,
+                errorString("send", action, "Internal plugin error"));
+    }
+
+    @Override
+    public Status removeFlow(Node node, Flow flow) {
+        String action = "remove";
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            return new Status(StatusCode.NOTACCEPTABLE,
+                    errorString("send", action, "Invalid node type"));
+        }
+        if (controller != null) {
+            ISwitch sw = controller.getSwitch((Long) node.getID());
+            if (sw != null) {
+                OFMessage msg = new FlowConverter(flow).getOFFlowMod(
+                        OFFlowMod.OFPFC_DELETE_STRICT, OFPort.OFPP_NONE);
+                Object result = sw.syncSend(msg);
+                if (result instanceof Boolean) {
+                    return ((Boolean) result == Boolean.TRUE) ?
+                            new Status(StatusCode.SUCCESS, null)
+                            : new Status(StatusCode.TIMEOUT,
+                                    errorString(null, action,
+                                            "Request Timed Out"));
+                } else if (result instanceof OFError) {
+                    return new Status(StatusCode.INTERNALERROR,
+                            errorString("program", action, Utils
+                            .getOFErrorString((OFError) result)));
+                } else {
+                    return new Status(StatusCode.INTERNALERROR,
+                            errorString("send", action, "Internal Error"));
+                }
+            } else {
+                return new Status(StatusCode.GONE,  errorString("send", action,
+                        "Switch is not available"));
+            }
+        }
+        return new Status(StatusCode.INTERNALERROR,
+                errorString("send", action, "Internal plugin error"));
+    }
+
+    @Override
+    public Status removeAllFlows(Node node) {
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private String errorString(String phase, String action, String cause) {
+        return "Failed to "
+                + ((phase != null) ? phase + " the " + action
+                        + " flow message: " : action + " the flow: ") + cause;
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowStatisticsConverter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowStatisticsConverter.java
new file mode 100644 (file)
index 0000000..72482f0
--- /dev/null
@@ -0,0 +1,88 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6StatsReply;
+import org.openflow.protocol.statistics.OFFlowStatisticsReply;
+import org.openflow.protocol.statistics.OFStatistics;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+
+/**
+ * Converts an openflow list of flow statistics in a SAL list of FlowOnNode objects
+ *
+ *
+ *
+ */
+public class FlowStatisticsConverter {
+    private List<OFStatistics> ofStatsList;
+    private List<FlowOnNode> flowOnNodeList;
+
+    public FlowStatisticsConverter(List<OFStatistics> statsList) {
+        if (statsList == null) {// || statsList.isEmpty()) {
+            this.ofStatsList = new ArrayList<OFStatistics>(1); // dummy list
+        } else {
+            this.ofStatsList = statsList; //new ArrayList<OFStatistics>(statsList);
+        }
+        this.flowOnNodeList = null;
+    }
+
+    public List<FlowOnNode> getFlowOnNodeList(Node node) {
+        if (ofStatsList != null && flowOnNodeList == null) {
+            flowOnNodeList = new ArrayList<FlowOnNode>();
+            FlowConverter flowConverter = null;
+            OFFlowStatisticsReply ofFlowStat;
+            V6StatsReply v6StatsReply;
+            for (OFStatistics ofStat : ofStatsList) {
+                FlowOnNode flowOnNode = null;
+                if (ofStat instanceof OFFlowStatisticsReply) {
+                    ofFlowStat = (OFFlowStatisticsReply) ofStat;
+                    flowConverter = new FlowConverter(ofFlowStat.getMatch(),
+                            ofFlowStat.getActions());
+                    Flow flow = flowConverter.getFlow(node);
+                    flow.setPriority(ofFlowStat.getPriority());
+                    flow.setIdleTimeout(ofFlowStat.getIdleTimeout());
+                    flow.setHardTimeout(ofFlowStat.getHardTimeout());
+                    flowOnNode = new FlowOnNode(flow);
+                    flowOnNode.setByteCount(ofFlowStat.getByteCount());
+                    flowOnNode.setPacketCount(ofFlowStat.getPacketCount());
+                    flowOnNode.setDurationSeconds(ofFlowStat
+                            .getDurationSeconds());
+                    flowOnNode.setDurationNanoseconds(ofFlowStat
+                            .getDurationNanoseconds());
+                } else if (ofStat instanceof V6StatsReply) {
+                    v6StatsReply = (V6StatsReply) ofStat;
+                    flowConverter = new FlowConverter(v6StatsReply.getMatch(),
+                            v6StatsReply.getActions());
+                    Flow flow = flowConverter.getFlow(node);
+                    flow.setPriority(v6StatsReply.getPriority());
+                    flow.setIdleTimeout(v6StatsReply.getIdleTimeout());
+                    flow.setHardTimeout(v6StatsReply.getHardTimeout());
+                    flowOnNode = new FlowOnNode(flow);
+                    flowOnNode.setByteCount(v6StatsReply.getByteCount());
+                    flowOnNode.setPacketCount(v6StatsReply.getPacketCount());
+                    flowOnNode.setDurationSeconds(v6StatsReply
+                            .getDurationSeconds());
+                    flowOnNode.setDurationNanoseconds(v6StatsReply
+                            .getDurationNanoseconds());
+                } else {
+                    continue;
+                }
+                flowOnNodeList.add(flowOnNode);
+            }
+        }
+        return flowOnNodeList;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryService.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryService.java
new file mode 100644 (file)
index 0000000..7041f44
--- /dev/null
@@ -0,0 +1,345 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimInternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFInventoryService;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Node.NodeIDType;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.TimeStamp;
+import org.opendaylight.controller.sal.core.Tables;
+import org.opendaylight.controller.sal.core.Actions;
+import org.opendaylight.controller.sal.core.Buffers;
+import org.opendaylight.controller.sal.core.Capabilities;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.inventory.IPluginInInventoryService;
+import org.opendaylight.controller.sal.inventory.IPluginOutInventoryService;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+
+/**
+ * The class describes inventory service protocol plugin. One instance per
+ * container of the network. Each instance gets container specific inventory
+ * events from InventoryServiceShim. It interacts with SAL to pass inventory
+ * data to the upper application.
+ *
+ *
+ */
+public class InventoryService implements IInventoryShimInternalListener,
+        IPluginInInventoryService, IOFInventoryService {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(InventoryService.class);
+    private Set<IPluginOutInventoryService> pluginOutInventoryServices = Collections
+            .synchronizedSet(new HashSet<IPluginOutInventoryService>());
+    private IController controller = null;
+    private ConcurrentMap<Node, Map<String, Property>> nodeProps; // properties are maintained in default container only
+    private ConcurrentMap<NodeConnector, Map<String, Property>> nodeConnectorProps; // properties are maintained in default container only
+    private boolean isDefaultContainer = false;
+
+    void setController(IController s) {
+        this.controller = s;
+    }
+
+    void unsetController(IController s) {
+        if (this.controller == s) {
+            this.controller = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    @SuppressWarnings("rawtypes")
+    void init(Component c) {
+        logger.trace("INIT called!");
+
+        Dictionary props = c.getServiceProperties();
+        if (props != null) {
+            String containerName = (String) props.get("containerName");
+            isDefaultContainer = containerName.equals(GlobalConstants.DEFAULT
+                    .toString());
+        }
+
+        nodeProps = new ConcurrentHashMap<Node, Map<String, Property>>();
+        nodeConnectorProps = new ConcurrentHashMap<NodeConnector, Map<String, Property>>();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one dependency
+     * become unsatisfied or when the component is shutting down because for
+     * example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        logger.trace("DESTROY called!");
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called and after
+     * the services provided by the class are registered in the service registry
+     *
+     */
+    void start() {
+        logger.trace("START called!");
+    }
+
+    /**
+     * Function called by the dependency manager before the services exported by
+     * the component are unregistered, this will be followed by a "destroy ()"
+     * calls
+     *
+     */
+    void stop() {
+        logger.trace("STOP called!");
+    }
+
+    public void setPluginOutInventoryServices(IPluginOutInventoryService service) {
+        logger.trace("Got a service set request {}", service);
+        if (this.pluginOutInventoryServices != null) {
+            this.pluginOutInventoryServices.add(service);
+        }
+    }
+
+    public void unsetPluginOutInventoryServices(
+            IPluginOutInventoryService service) {
+        logger.trace("Got a service UNset request");
+        if (this.pluginOutInventoryServices != null) {
+            this.pluginOutInventoryServices.remove(service);
+        }
+    }
+
+    protected Node OFSwitchToNode(ISwitch sw) {
+        Node node = null;
+        Object id = sw.getId();
+
+        try {
+            node = new Node(NodeIDType.OPENFLOW, id);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+
+        return node;
+    }
+
+    /**
+     * Retrieve nodes from openflow
+     */
+    @Override
+    public ConcurrentMap<Node, Map<String, Property>> getNodeProps() {
+        if (nodeProps == null)
+            return null;
+        Map<Long, ISwitch> switches = controller.getSwitches();
+        for (Map.Entry<Long, ISwitch> entry : switches.entrySet()) {
+               ISwitch sw = entry.getValue();
+            Node node = OFSwitchToNode(sw);
+            Map<String, Property> propMap = null;
+            if (isDefaultContainer) {
+                propMap = new HashMap<String, Property>();
+                byte tables = sw.getTables();
+                Tables t = new Tables(tables);
+                if (t != null) {
+                       propMap.put(Tables.TablesPropName,t);
+                }
+                int cap = sw.getCapabilities();
+                Capabilities c = new Capabilities(cap);
+                if (c != null) {
+                       propMap.put(Capabilities.CapabilitiesPropName, c);
+                }
+                int act = sw.getActions();
+                Actions a = new Actions(act);
+                if (a != null) {
+                       propMap.put(Actions.ActionsPropName,a);
+                }
+                int buffers = sw.getBuffers();
+                Buffers b = new Buffers(buffers);
+                if (b != null) {
+                       propMap.put(Buffers.BuffersPropName,b);
+                }
+                Date connectedSince = sw.getConnectedDate();
+                Long connectedSinceTime = (connectedSince == null) ? 0
+                        : connectedSince.getTime();
+                TimeStamp timeStamp = new TimeStamp(connectedSinceTime,
+                        "connectedSince");
+                propMap.put(TimeStamp.TimeStampPropName, timeStamp);
+                nodeProps.put(node, propMap);
+            }
+        }
+        return nodeProps;
+    }
+
+    @Override
+    public ConcurrentMap<NodeConnector, Map<String, Property>> getNodeConnectorProps(
+            Boolean refresh) {
+        if (nodeConnectorProps == null)
+            return null;
+
+        if (isDefaultContainer && refresh) {
+            Map<Long, ISwitch> switches = controller.getSwitches();
+            for (ISwitch sw : switches.values()) {
+                Map<NodeConnector, Set<Property>> ncProps = InventoryServiceHelper
+                        .OFSwitchToProps(sw);
+                for (Map.Entry<NodeConnector, Set<Property>> entry : ncProps
+                        .entrySet()) {
+                    updateNodeConnector(entry.getKey(), UpdateType.ADDED, entry
+                            .getValue());
+                }
+            }
+        }
+
+        return nodeConnectorProps;
+    }
+
+    @Override
+    public void updateNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props) {
+        logger.trace("NodeConnector id " + nodeConnector.getID()
+                + " type " + nodeConnector.getType() + " "
+                + type.getName() + " for Node id "
+                + nodeConnector.getNode().getID());
+
+        if (nodeConnectorProps == null)
+            return;
+
+        synchronized (nodeConnectorProps) {
+            Map<String, Property> propMap = nodeConnectorProps
+                    .get(nodeConnector);
+            switch (type) {
+            case ADDED:
+            case CHANGED:
+                if (propMap == null)
+                    propMap = new HashMap<String, Property>();
+
+                if (props != null) {
+                    for (Property prop : props) {
+                        propMap.put(prop.getName(), prop);
+                    }
+                }
+                nodeConnectorProps.put(nodeConnector, propMap);
+                break;
+            case REMOVED:
+                nodeConnectorProps.remove(nodeConnector);
+                break;
+            default:
+                return;
+            }
+        }
+
+        // update sal and discovery
+        synchronized (pluginOutInventoryServices) {
+            for (IPluginOutInventoryService service : pluginOutInventoryServices) {
+                service.updateNodeConnector(nodeConnector, type, props);
+            }
+        }
+    }
+
+    private void addNode(Node node, Set<Property> props) {
+        logger.trace("{} added", node);
+        if (nodeProps == null)
+            return;
+
+        // update local cache
+        Map<String, Property> propMap = new HashMap<String, Property>();
+        for (Property prop : props) {
+            propMap.put(prop.getName(), prop);
+        }
+        nodeProps.put(node, propMap);
+
+        // update sal
+        synchronized (pluginOutInventoryServices) {
+            for (IPluginOutInventoryService service : pluginOutInventoryServices) {
+                service.updateNode(node, UpdateType.ADDED, props);
+            }
+        }
+    }
+
+    private void removeNode(Node node) {
+        logger.trace("{} removed", node);
+        if (nodeProps == null)
+            return;
+
+        // update local cache
+        nodeProps.remove(node);
+
+        Set<NodeConnector> removeSet = new HashSet<NodeConnector>();
+        for (NodeConnector nodeConnector : nodeConnectorProps.keySet()) {
+            if (nodeConnector.getNode().equals(node)) {
+                removeSet.add(nodeConnector);
+            }
+        }
+        for (NodeConnector nodeConnector : removeSet) {
+            nodeConnectorProps.remove(nodeConnector);
+        }
+
+        // update sal
+        synchronized (pluginOutInventoryServices) {
+            for (IPluginOutInventoryService service : pluginOutInventoryServices) {
+                service.updateNode(node, UpdateType.REMOVED, null);
+            }
+        }
+    }
+
+    /*
+     * Function called by other protocol plugin modules to notify Inventory Service
+     * that a property has changed for the specified switch
+     */
+    @Override
+    public void updateSwitchProperty(Long switchId, Set<Property> propSet) {
+        // update local cache
+        Node node = OFSwitchToNode(controller.getSwitches().get(switchId));
+        Map<String, Property> propMap = nodeProps.get(node);
+        if (propMap == null)
+            propMap = new HashMap<String, Property>();
+        for (Property prop : propSet) {
+            propMap.put(prop.getName(), prop);
+        }
+        nodeProps.put(node, propMap);
+
+        // update sal
+        synchronized (pluginOutInventoryServices) {
+            for (IPluginOutInventoryService service : pluginOutInventoryServices) {
+                service.updateNode(node, UpdateType.CHANGED, propSet);
+            }
+        }
+    }
+
+    @Override
+    public void updateNode(Node node, UpdateType type, Set<Property> props) {
+        switch (type) {
+        case ADDED:
+            addNode(node, props);
+            break;
+        case REMOVED:
+            removeNode(node);
+            break;
+        default:
+            break;
+        }
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryServiceHelper.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryServiceHelper.java
new file mode 100644 (file)
index 0000000..33afa94
--- /dev/null
@@ -0,0 +1,167 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.AdvertisedBandwidth;
+import org.opendaylight.controller.sal.core.SupportedBandwidth;
+import org.opendaylight.controller.sal.core.PeerBandwidth;
+import org.opendaylight.controller.sal.core.Config;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPhysicalPort.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortFeatures;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+
+/**
+ * The class provides helper functions to retrieve inventory properties from
+ * OpenFlow messages
+ */
+public class InventoryServiceHelper {
+    /*
+     * Returns BandWidth property from OpenFlow OFPhysicalPort features
+     */
+    public static Bandwidth OFPortToBandWidth(int portFeatures) {
+        Bandwidth bw = null;
+        int value = portFeatures
+                & (OFPortFeatures.OFPPF_10MB_FD.getValue()
+                        | OFPortFeatures.OFPPF_10MB_HD.getValue()
+                        | OFPortFeatures.OFPPF_100MB_FD.getValue()
+                        | OFPortFeatures.OFPPF_100MB_HD.getValue()
+                        | OFPortFeatures.OFPPF_1GB_FD.getValue()
+                        | OFPortFeatures.OFPPF_1GB_HD.getValue() | OFPortFeatures.OFPPF_10GB_FD
+                        .getValue());
+
+        switch (value) {
+        case 1:
+        case 2:
+            bw = new Bandwidth(Bandwidth.BW10Mbps);
+            break;
+        case 4:
+        case 8:
+            bw = new Bandwidth(Bandwidth.BW100Mbps);
+            break;
+        case 16:
+        case 32:
+            bw = new Bandwidth(Bandwidth.BW1Gbps);
+            break;
+        case 64:
+            bw = new Bandwidth(Bandwidth.BW10Gbps);
+            break;
+        default:
+            break;
+        }
+        return bw;
+    }
+
+    /*
+     * Returns Config property from OpenFLow OFPhysicalPort config
+     */
+    public static Config OFPortToConfig(int portConfig) {
+        Config config;
+        if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & portConfig) != 0)
+            config = new Config(Config.ADMIN_DOWN);
+        else
+            config = new Config(Config.ADMIN_UP);
+        return config;
+    }
+
+    /*
+     * Returns State property from OpenFLow OFPhysicalPort state
+     */
+    public static State OFPortToState(int portState) {
+        State state;
+        if ((OFPortState.OFPPS_LINK_DOWN.getValue() & portState) != 0)
+            state = new State(State.EDGE_DOWN);
+        else
+            state = new State(State.EDGE_UP);
+        return state;
+    }
+
+    /*
+     * Returns set of properties from OpenFLow OFPhysicalPort
+     */
+    public static Set<Property> OFPortToProps(OFPhysicalPort port) {
+        Set<Property> props = new HashSet<Property>();
+        Bandwidth bw = InventoryServiceHelper.OFPortToBandWidth(port
+                .getCurrentFeatures());
+        if (bw != null) {
+            props.add(bw);
+        }
+        
+        Bandwidth abw = InventoryServiceHelper.OFPortToBandWidth(port.getAdvertisedFeatures());
+        if (abw != null) {
+               AdvertisedBandwidth a = new AdvertisedBandwidth(abw.getValue());
+               if (a != null) {
+                       props.add(a);
+               }
+        }
+        Bandwidth sbw = InventoryServiceHelper.OFPortToBandWidth(port.getSupportedFeatures());
+        if (sbw != null) {
+               SupportedBandwidth s = new SupportedBandwidth(sbw.getValue());
+               if (s != null) {
+                       props.add(s);
+               }
+        }
+        Bandwidth pbw = InventoryServiceHelper.OFPortToBandWidth(port.getPeerFeatures());
+        if (pbw != null) {
+               PeerBandwidth p = new PeerBandwidth(pbw.getValue());
+               if (p != null) {
+                       props.add(p);
+               }
+        }
+        props.add(new Name(port.getName()));
+        props.add(InventoryServiceHelper.OFPortToConfig(port.getConfig()));
+        props.add(InventoryServiceHelper.OFPortToState(port.getState()));
+        return props;
+    }
+
+    /*
+     * Returns set of properties for each nodeConnector in an OpenFLow switch
+     */
+    public static Map<NodeConnector, Set<Property>> OFSwitchToProps(ISwitch sw) {
+        Map<NodeConnector, Set<Property>> ncProps = new HashMap<NodeConnector, Set<Property>>();
+
+        if (sw == null) {
+            return ncProps;
+        }
+
+        Node node = NodeCreator.createOFNode(sw.getId());
+        if (node == null) {
+            return ncProps;
+        }
+
+        Set<Property> props;
+        NodeConnector nodeConnector;
+        OFPhysicalPort port;
+        Map<Short, OFPhysicalPort> ports = sw.getPhysicalPorts();
+        for (Map.Entry<Short, OFPhysicalPort> entry : ports.entrySet()) {
+            nodeConnector = PortConverter.toNodeConnector(entry.getKey(), node);
+            port = entry.getValue();
+            props = InventoryServiceHelper.OFPortToProps(port);
+            ncProps.put(nodeConnector, props);
+        }
+
+        return ncProps;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryServiceShim.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/InventoryServiceShim.java
new file mode 100644 (file)
index 0000000..cd05f31
--- /dev/null
@@ -0,0 +1,449 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimExternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimInternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsManager;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IMessageListener;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitchStateListener;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Actions;
+import org.opendaylight.controller.sal.core.Buffers;
+import org.opendaylight.controller.sal.core.Capabilities;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Tables;
+import org.opendaylight.controller.sal.core.Node.NodeIDType;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.TimeStamp;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+
+/**
+ * The class describes a shim layer that bridges inventory events from Openflow
+ * core to various listeners. The notifications are filtered based on container
+ * configurations.
+ *
+ *
+ */
+public class InventoryServiceShim implements IContainerListener,
+        IMessageListener, ISwitchStateListener {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(InventoryServiceShim.class);
+    private IController controller = null;
+    private ConcurrentMap<String, IInventoryShimInternalListener> inventoryShimInternalListeners = new ConcurrentHashMap<String, IInventoryShimInternalListener>();
+    private List<IInventoryShimExternalListener> inventoryShimExternalListeners = new CopyOnWriteArrayList<IInventoryShimExternalListener>();
+    private ConcurrentMap<NodeConnector, List<String>> containerMap = new ConcurrentHashMap<NodeConnector, List<String>>();
+    private IOFStatisticsManager statsMgr;
+
+    void setController(IController s) {
+        this.controller = s;
+    }
+
+    void unsetController(IController s) {
+        if (this.controller == s) {
+            this.controller = null;
+        }
+    }
+
+    void setStatisticsManager(IOFStatisticsManager s) {
+        this.statsMgr = s;
+    }
+
+    void unsetStatisticsManager(IOFStatisticsManager s) {
+        if (this.statsMgr == s) {
+            this.statsMgr = null;
+        }
+    }
+
+    void setInventoryShimInternalListener(Map<?, ?> props,
+            IInventoryShimInternalListener s) {
+        if (props == null) {
+            logger.error("Didn't receive the service properties");
+            return;
+        }
+        String containerName = (String) props.get("containerName");
+        if (containerName == null) {
+            logger.error("containerName not supplied");
+            return;
+        }
+        if ((this.inventoryShimInternalListeners != null)
+                && !this.inventoryShimInternalListeners.containsKey(s)) {
+            this.inventoryShimInternalListeners.put(containerName, s);
+            logger.trace("Added inventoryShimInternalListener for container:"
+                    + containerName);
+        }
+    }
+
+    void unsetInventoryShimInternalListener(Map<?, ?> props,
+            IInventoryShimInternalListener s) {
+        if (props == null) {
+            logger.error("Didn't receive the service properties");
+            return;
+        }
+        String containerName = (String) props.get("containerName");
+        if (containerName == null) {
+            logger.error("containerName not supplied");
+            return;
+        }
+        if ((this.inventoryShimInternalListeners != null)
+                && this.inventoryShimInternalListeners.containsKey(s)) {
+            this.inventoryShimInternalListeners.remove(containerName);
+            logger
+                    .trace("Removed inventoryShimInternalListener for container: "
+                            + containerName);
+        }
+    }
+
+    void setInventoryShimExternalListener(IInventoryShimExternalListener s) {
+        logger.trace("Set inventoryShimExternalListener");
+        if ((this.inventoryShimExternalListeners != null)
+                && !this.inventoryShimExternalListeners.contains(s)) {
+            this.inventoryShimExternalListeners.add(s);
+        }
+    }
+
+    void unsetInventoryShimExternalListener(IInventoryShimExternalListener s) {
+        if ((this.inventoryShimExternalListeners != null)
+                && this.inventoryShimExternalListeners.contains(s)) {
+            this.inventoryShimExternalListeners.remove(s);
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        this.controller.addMessageListener(OFType.PORT_STATUS, this);
+        this.controller.addSwitchStateListener(this);
+    }
+
+    /**
+     * Function called after registering the
+     * service in OSGi service registry.
+     */
+    void started() {
+        /* Start with existing switches */
+        startService();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        this.controller.removeMessageListener(OFType.PACKET_IN, this);
+        this.controller.removeSwitchStateListener(this);
+
+        this.inventoryShimInternalListeners.clear();
+        this.containerMap.clear();
+        this.controller = null;
+    }
+
+    @Override
+    public void receive(ISwitch sw, OFMessage msg) {
+        try {
+            if (msg instanceof OFPortStatus) {
+                handlePortStatusMessage(sw, (OFPortStatus) msg);
+            }
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        return;
+    }
+
+    protected void handlePortStatusMessage(ISwitch sw, OFPortStatus m)
+            throws ConstructionException {
+        Node node = new Node(NodeIDType.OPENFLOW, sw.getId());
+        NodeConnector nodeConnector = PortConverter.toNodeConnector(m.getDesc()
+                .getPortNumber(), node);
+        UpdateType type = null;
+
+        if (m.getReason() == (byte) OFPortReason.OFPPR_ADD.ordinal()) {
+            type = UpdateType.ADDED;
+        } else if (m.getReason() == (byte) OFPortReason.OFPPR_DELETE.ordinal()) {
+            type = UpdateType.REMOVED;
+        } else if (m.getReason() == (byte) OFPortReason.OFPPR_MODIFY.ordinal()) {
+            type = UpdateType.CHANGED;
+        }
+
+        if (type != null) {
+            // get node connector properties
+            Set<Property> props = InventoryServiceHelper.OFPortToProps(m
+                    .getDesc());
+            notifyInventoryShimListener(nodeConnector, type, props);
+        }
+    }
+
+    @Override
+    public void switchAdded(ISwitch sw) {
+        if (sw == null)
+            return;
+
+        // Add all the nodeConnectors of this switch
+        Map<NodeConnector, Set<Property>> ncProps = InventoryServiceHelper
+                .OFSwitchToProps(sw);
+        for (Map.Entry<NodeConnector, Set<Property>> entry : ncProps.entrySet()) {
+            notifyInventoryShimListener(entry.getKey(), UpdateType.ADDED, entry
+                    .getValue());
+        }
+
+        // Add this node
+        addNode(sw);
+    }
+
+    @Override
+    public void switchDeleted(ISwitch sw) {
+        if (sw == null)
+            return;
+
+        removeNode(sw);
+    }
+
+    @Override
+    public void containerModeUpdated(UpdateType t) {
+        // do nothing
+    }
+
+    @Override
+    public void tagUpdated(String containerName, Node n, short oldTag,
+            short newTag, UpdateType t) {
+    }
+
+    @Override
+    public void containerFlowUpdated(String containerName,
+            ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) {
+    }
+
+    @Override
+    public void nodeConnectorUpdated(String containerName, NodeConnector p,
+            UpdateType t) {
+        if (this.containerMap == null) {
+            logger.error("containerMap is NULL");
+            return;
+        }
+        List<String> containers = this.containerMap.get(p);
+        if (containers == null) {
+            containers = new CopyOnWriteArrayList<String>();
+        }
+        boolean updateMap = false;
+        switch (t) {
+        case ADDED:
+            if (!containers.contains(containerName)) {
+                containers.add(containerName);
+                updateMap = true;
+            }
+            break;
+        case REMOVED:
+            if (containers.contains(containerName)) {
+                containers.remove(containerName);
+                updateMap = true;
+            }
+            break;
+        case CHANGED:
+            break;
+        }
+        if (updateMap) {
+            if (containers.isEmpty()) {
+                // Do cleanup to reduce memory footprint if no
+                // elements to be tracked
+                this.containerMap.remove(p);
+            } else {
+                this.containerMap.put(p, containers);
+            }
+        }
+
+        // notify InventoryService
+        notifyInventoryShimInternalListener(containerName, p, t, null);
+    }
+
+    private void notifyInventoryShimExternalListener(Node node,
+            UpdateType type, Set<Property> props) {
+        for (IInventoryShimExternalListener s : this.inventoryShimExternalListeners) {
+            s.updateNode(node, type, props);
+        }
+    }
+
+    private void notifyInventoryShimExternalListener(
+            NodeConnector nodeConnector, UpdateType type, Set<Property> props) {
+        for (IInventoryShimExternalListener s : this.inventoryShimExternalListeners) {
+            s.updateNodeConnector(nodeConnector, type, props);
+        }
+    }
+
+    private void notifyInventoryShimInternalListener(String container,
+            NodeConnector nodeConnector, UpdateType type, Set<Property> props) {
+        IInventoryShimInternalListener inventoryShimInternalListener = inventoryShimInternalListeners
+                .get(container);
+        if (inventoryShimInternalListener != null) {
+            inventoryShimInternalListener.updateNodeConnector(nodeConnector,
+                    type, props);
+            logger.trace(type + " " + nodeConnector + " on container "
+                    + container);
+        }
+    }
+
+    /*
+     *  Notify all internal and external listeners
+     */
+    private void notifyInventoryShimListener(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props) {
+        // Always notify default InventoryService. Store properties in default one.
+        notifyInventoryShimInternalListener(GlobalConstants.DEFAULT.toString(),
+                nodeConnector, type, props);
+
+        // Now notify other containers
+        List<String> containers = containerMap.get(nodeConnector);
+        if (containers != null) {
+            for (String container : containers) {
+                // no property stored in container components.
+                notifyInventoryShimInternalListener(container, nodeConnector,
+                        type, null);
+            }
+        }
+
+        // Notify DiscoveryService
+        notifyInventoryShimExternalListener(nodeConnector, type, props);
+    }
+
+    /*
+     *  Notify all internal and external listeners
+     */
+    private void notifyInventoryShimListener(Node node, UpdateType type,
+            Set<Property> props) {
+        switch (type) {
+        case ADDED:
+            // Notify only the default Inventory Service
+            IInventoryShimInternalListener inventoryShimDefaultListener = inventoryShimInternalListeners
+                    .get(GlobalConstants.DEFAULT.toString());
+            if (inventoryShimDefaultListener != null) {
+                inventoryShimDefaultListener.updateNode(node, type, props);
+            }
+            break;
+        case REMOVED:
+            // Notify all Inventory Service containers
+            for (IInventoryShimInternalListener inventoryShimInternalListener : inventoryShimInternalListeners
+                    .values()) {
+                inventoryShimInternalListener.updateNode(node, type, null);
+            }
+            break;
+        default:
+            break;
+        }
+
+        // Notify external listener
+        notifyInventoryShimExternalListener(node, type, props);
+    }
+
+    private void addNode(ISwitch sw) {
+        Node node;
+        try {
+            node = new Node(NodeIDType.OPENFLOW, sw.getId());
+        } catch (ConstructionException e) {
+            logger.error("{}", e.getMessage());
+            return;
+        }
+
+        UpdateType type = UpdateType.ADDED;
+
+        Set<Property> props = new HashSet<Property>();
+        Long sid = (Long) node.getID();
+
+        Date connectedSince = controller.getSwitches().get(sid)
+                .getConnectedDate();
+        Long connectedSinceTime = (connectedSince == null) ? 0 : connectedSince
+                .getTime();
+        props.add(new TimeStamp(connectedSinceTime, "connectedSince"));
+
+        String name = "";
+        if (statsMgr != null && statsMgr.getOFDescStatistics(sid) != null) {
+            List<OFStatistics> stats = statsMgr.getOFDescStatistics(sid);
+            if (stats.size() > 0) {
+                name = ((OFDescriptionStatistics) stats.get(0))
+                        .getSerialNumber();
+            }
+        }
+        props.add(new Name(name));
+        
+        byte tables = sw.getTables();
+        Tables t = new Tables(tables);
+        if (t != null) {
+               props.add(t);
+        }
+        int cap = sw.getCapabilities();
+        Capabilities c = new Capabilities(cap);
+        if (c != null) {
+               props.add(c);
+        }
+        int act = sw.getActions();
+        Actions a = new Actions(act);
+        if (a != null) {
+               props.add(a);
+        }
+        int buffers = sw.getBuffers();
+        Buffers b = new Buffers(buffers);
+        if (b != null) {
+               props.add(b);
+        }
+        // Notify all internal and external listeners
+        notifyInventoryShimListener(node, type, props);
+    }
+
+    private void removeNode(ISwitch sw) {
+        Node node;
+        try {
+            node = new Node(NodeIDType.OPENFLOW, sw.getId());
+        } catch (ConstructionException e) {
+            logger.error("{}", e.getMessage());
+            return;
+        }
+
+        UpdateType type = UpdateType.REMOVED;
+
+        // Notify all internal and external listeners
+        notifyInventoryShimListener(node, type, null);
+    }
+
+    private void startService() {
+        // Get a snapshot of all the existing switches
+        Map<Long, ISwitch> switches = this.controller.getSwitches();
+        for (ISwitch sw : switches.values()) {
+            switchAdded(sw);
+        }
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/OFStatisticsManager.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/OFStatisticsManager.java
new file mode 100644 (file)
index 0000000..bd31f8b
--- /dev/null
@@ -0,0 +1,1072 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.IInventoryShimExternalListener;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFInventoryService;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsManager;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.opendaylight.controller.protocol_plugin.openflow.core.ISwitch;
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6Match;
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6StatsReply;
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6StatsRequest;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFAggregateStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFFlowStatisticsReply;
+import org.openflow.protocol.statistics.OFFlowStatisticsRequest;
+import org.openflow.protocol.statistics.OFPortStatisticsReply;
+import org.openflow.protocol.statistics.OFPortStatisticsRequest;
+import org.openflow.protocol.statistics.OFQueueStatisticsRequest;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.protocol.statistics.OFVendorStatistics;
+import org.openflow.util.HexString;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+
+/**
+ * It periodically polls the different OF statistics from the OF switches
+ * and caches them for quick retrieval for the above layers' modules
+ * It also provides an API to directly query the switch about the statistics
+ *
+ *
+ *
+ */
+public class OFStatisticsManager implements IOFStatisticsManager,
+        IInventoryShimExternalListener, CommandProvider {
+    private static final Logger log = LoggerFactory
+            .getLogger(OFStatisticsManager.class);
+    private static final int initialSize = 64;
+    private static final long flowStatsPeriod = 10000;
+    private static final long descriptionStatsPeriod = 60000;
+    private static final long portStatsPeriod = 5000;
+    private long statisticsTimeout = 4000;
+    private static final long tickPeriod = 1000;
+    private static short statisticsTickNumber = (short) (flowStatsPeriod / tickPeriod);
+    private static short descriptionTickNumber = (short) (descriptionStatsPeriod / tickPeriod);
+    private static short portTickNumber = (short) (portStatsPeriod / tickPeriod);
+    private static short factoredSamples = (short) 2;
+    private static short counter = 1;
+    private IController controller = null;
+    private ConcurrentMap<Long, List<OFStatistics>> flowStatistics;
+    private ConcurrentMap<Long, List<OFStatistics>> descStatistics;
+    private ConcurrentMap<Long, List<OFStatistics>> portStatistics;
+    private List<OFStatistics> dummyList;
+    private ConcurrentMap<Long, StatisticsTicks> statisticsTimerTicks;
+    protected BlockingQueue<StatsRequest> pendingStatsRequests;
+    protected BlockingQueue<Long> switchPortStatsUpdated;
+    private Thread statisticsCollector;
+    private Thread txRatesUpdater;
+    private Timer statisticsTimer;
+    private TimerTask statisticsTimerTask;
+    private ConcurrentMap<Long, Boolean> switchSupportsVendorExtStats;
+    private Map<Long, String> switchNamesDB;
+    private Map<Long, Map<Short, TxRates>> txRates; // Per port sampled (every portStatsPeriod) transmit rate
+
+    /**
+     * The object containing the latest factoredSamples tx rate samples
+     * for a given switch port
+     */
+    protected class TxRates {
+        Deque<Long> sampledTxBytes; // contains the latest factoredSamples sampled transmitted bytes
+
+        public TxRates() {
+            sampledTxBytes = new LinkedBlockingDeque<Long>();
+        }
+
+        public void update(Long txBytes) {
+            /*
+             * Based on how many samples our average works on,
+             * we might have to remove the oldest sample
+             */
+            if (sampledTxBytes.size() == factoredSamples) {
+                sampledTxBytes.removeLast();
+            }
+
+            // Add the latest sample to the top of the queue
+            sampledTxBytes.addFirst(txBytes);
+        }
+
+        /**
+         * Returns the average transmit rate in bps
+         * @return the average transmit rate [bps]
+         */
+        public long getAverageTxRate() {
+            long average = 0;
+            /*
+             * If we cannot provide the value for the time window length set
+             */
+            if (sampledTxBytes.size() < factoredSamples) {
+                return average;
+            }
+            long increment = (long) (sampledTxBytes.getFirst() - sampledTxBytes
+                    .getLast());
+            long timePeriod = (long) (factoredSamples * portStatsPeriod)
+                    / (long) tickPeriod;
+            average = (8 * increment) / timePeriod;
+            return average;
+        }
+    }
+
+    public void setController(IController core) {
+        this.controller = core;
+    }
+
+    public void unsetController(IController core) {
+        if (this.controller == core) {
+            this.controller = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+        // Start managed timers
+        statisticsTimer.scheduleAtFixedRate(statisticsTimerTask, 0, tickPeriod);
+
+        // Start statistics collector thread
+        statisticsCollector.start();
+
+        // Start bandwidth utilization computer thread
+        txRatesUpdater.start();
+
+        // OSGI console
+        registerWithOSGIConsole();
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+        // Stop managed timers
+        statisticsTimer.cancel();
+    }
+
+    public OFStatisticsManager() {
+        flowStatistics = new ConcurrentHashMap<Long, List<OFStatistics>>();
+        descStatistics = new ConcurrentHashMap<Long, List<OFStatistics>>();
+        portStatistics = new ConcurrentHashMap<Long, List<OFStatistics>>();
+        dummyList = new ArrayList<OFStatistics>(1);
+        switchNamesDB = new HashMap<Long, String>();
+        statisticsTimerTicks = new ConcurrentHashMap<Long, StatisticsTicks>(
+                initialSize);
+        pendingStatsRequests = new LinkedBlockingQueue<StatsRequest>(
+                initialSize);
+        switchPortStatsUpdated = new LinkedBlockingQueue<Long>(initialSize);
+        switchSupportsVendorExtStats = new ConcurrentHashMap<Long, Boolean>(
+                initialSize);
+        txRates = new HashMap<Long, Map<Short, TxRates>>(initialSize);
+
+        // Initialize managed timers
+        statisticsTimer = new Timer();
+        statisticsTimerTask = new TimerTask() {
+            @Override
+            public void run() {
+                decrementTicks();
+            }
+        };
+
+        // Initialize Statistics collector thread
+        statisticsCollector = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        StatsRequest req = pendingStatsRequests.take();
+                        acquireStatistics(req.switchId, req.type,
+                                statisticsTimeout);
+                    } catch (InterruptedException e) {
+                        log
+                                .warn("Flow Statistics Collector thread interrupted");
+                    }
+                }
+            }
+        }, "Statistics Collector");
+
+        // Initialize Tx Rate Updater thread
+        txRatesUpdater = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        long switchId = switchPortStatsUpdated.take();
+                        updatePortsTxRate(switchId);
+                    } catch (InterruptedException e) {
+                        log.warn("TX Rate Updater thread interrupted");
+                    }
+                }
+            }
+        }, "TX Rate Updater");
+
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    private static class StatsRequest {
+        protected Long switchId;
+        protected OFStatisticsType type;
+
+        public StatsRequest(Long d, OFStatisticsType t) {
+            switchId = d;
+            type = t;
+        }
+
+        public String toString() {
+            return "SReq = {switchId=" + switchId + ", type=" + type + "}";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result
+                    + ((switchId == null) ? 0 : switchId.hashCode());
+            result = prime * result + ((type == null) ? 0 : type.ordinal());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            StatsRequest other = (StatsRequest) obj;
+            if (switchId == null) {
+                if (other.switchId != null) {
+                    return false;
+                }
+            } else if (!switchId.equals(other.switchId)) {
+                return false;
+            }
+            if (type != other.type) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    private void addStatisticsTicks(Long switchId) {
+        switchSupportsVendorExtStats.put(switchId, Boolean.TRUE); // Assume switch supports Vendor extension stats
+        statisticsTimerTicks.put(switchId, new StatisticsTicks(true));
+        log.info("Added Switch {} to target pool", HexString
+                .toHexString(switchId.longValue()));
+    }
+
+    protected static class StatisticsTicks {
+        private short flowStatisticsTicks;
+        private short descriptionTicks;
+        private short portStatisticsTicks;
+
+        public StatisticsTicks(boolean scattered) {
+            if (scattered) {
+                // scatter bursts by statisticsTickPeriod
+                if (++counter < 0) {
+                    counter = 0;
+                } // being paranoid here
+                flowStatisticsTicks = (short) (1 + counter
+                        % statisticsTickNumber);
+                descriptionTicks = (short) (1 + counter % descriptionTickNumber);
+                portStatisticsTicks = (short) (1 + counter % portTickNumber);
+            } else {
+                flowStatisticsTicks = statisticsTickNumber;
+                descriptionTicks = descriptionTickNumber;
+                portStatisticsTicks = portTickNumber;
+            }
+        }
+
+        public boolean decrementFlowTicksIsZero() {
+            // Please ensure no code is inserted between the if check and the flowStatisticsTicks reset
+            if (--flowStatisticsTicks == 0) {
+                flowStatisticsTicks = statisticsTickNumber;
+                return true;
+            }
+            return false;
+        }
+
+        public boolean decrementDescTicksIsZero() {
+            // Please ensure no code is inserted between the if check and the descriptionTicks reset
+            if (--descriptionTicks == 0) {
+                descriptionTicks = descriptionTickNumber;
+                return true;
+            }
+            return false;
+        }
+
+        public boolean decrementPortTicksIsZero() {
+            // Please ensure no code is inserted between the if check and the descriptionTicks reset
+            if (--portStatisticsTicks == 0) {
+                portStatisticsTicks = portTickNumber;
+                return true;
+            }
+            return false;
+        }
+
+        public String toString() {
+            return "{fT=" + flowStatisticsTicks + ",dT=" + descriptionTicks
+                    + ",pT=" + portStatisticsTicks + "}";
+        }
+    }
+
+    private void printInfoMessage(String type, StatsRequest request) {
+        log
+                .info(
+                        type
+                                + " stats request not inserted for switch: {}. Queue size: {}. Collector state: {}.",
+                        new Object[] { HexString.toHexString(request.switchId),
+                                pendingStatsRequests.size(),
+                                statisticsCollector.getState().toString() });
+    }
+
+    protected void decrementTicks() {
+        StatsRequest request = null;
+        for (Map.Entry<Long, StatisticsTicks> entry : statisticsTimerTicks
+                .entrySet()) {
+            StatisticsTicks clock = entry.getValue();
+            Long switchId = entry.getKey();
+            if (clock.decrementFlowTicksIsZero() == true) {
+                request = (switchSupportsVendorExtStats.get(switchId) == Boolean.TRUE) ? new StatsRequest(
+                        switchId, OFStatisticsType.VENDOR)
+                        : new StatsRequest(switchId, OFStatisticsType.FLOW);
+                // If a request for this switch is already in the queue, skip to add this new request
+                if (!pendingStatsRequests.contains(request)
+                        && false == pendingStatsRequests.offer(request)) {
+                    printInfoMessage("Flow", request);
+                }
+            }
+
+            if (clock.decrementDescTicksIsZero() == true) {
+                request = new StatsRequest(switchId, OFStatisticsType.DESC);
+                // If a request for this switch is already in the queue, skip to add this new request
+                if (!pendingStatsRequests.contains(request)
+                        && false == pendingStatsRequests.offer(request)) {
+                    printInfoMessage("Description", request);
+                }
+            }
+
+            if (clock.decrementPortTicksIsZero() == true) {
+                request = new StatsRequest(switchId, OFStatisticsType.PORT);
+                // If a request for this switch is already in the queue, skip to add this new request
+                if (!pendingStatsRequests.contains(request)
+                        && false == pendingStatsRequests.offer(request)) {
+                    printInfoMessage("Port", request);
+                }
+            }
+        }
+    }
+
+    private void removeStatsRequestTasks(Long switchId) {
+        log.info("Cleaning Statistics database for switch "
+                + HexEncode.longToHexString(switchId));
+        // To be safe, let's attempt removal of both VENDOR and FLOW request. It does not hurt
+        pendingStatsRequests.remove(new StatsRequest(switchId,
+                OFStatisticsType.VENDOR));
+        pendingStatsRequests.remove(new StatsRequest(switchId,
+                OFStatisticsType.FLOW));
+        pendingStatsRequests.remove(new StatsRequest(switchId,
+                OFStatisticsType.DESC));
+        pendingStatsRequests.remove(new StatsRequest(switchId,
+                OFStatisticsType.PORT));
+        // Take care of the TX rate databases
+        switchPortStatsUpdated.remove(switchId);
+        txRates.remove(switchId);
+    }
+
+    private void clearFlowStatsAndTicks(Long switchId) {
+        statisticsTimerTicks.remove(switchId);
+        removeStatsRequestTasks(switchId);
+        flowStatistics.remove(switchId);
+        log.info("Statistics removed for switch "
+                + HexString.toHexString(switchId));
+    }
+
+    private void acquireStatistics(Long switchId, OFStatisticsType statType,
+            long timeout) {
+
+        // Query the switch on all matches
+        List<OFStatistics> values = this.acquireStatistics(switchId, statType,
+                null, timeout);
+
+        // Update local caching database if got a valid response
+        if (values != null && !values.isEmpty()) {
+            if ((statType == OFStatisticsType.FLOW)
+                    || (statType == OFStatisticsType.VENDOR)) {
+                flowStatistics.put(switchId, values);
+            } else if (statType == OFStatisticsType.DESC) {
+                if ((switchNamesDB != null)
+                        && switchNamesDB.containsKey(switchId)) {
+                    // Check if user manually configured a name for the switch
+                    for (OFStatistics entry : values) {
+                        OFDescriptionStatistics reply = (OFDescriptionStatistics) entry;
+                        reply.setSerialNumber(switchNamesDB.get(switchId));
+                    }
+                }
+                // check if notification is needed
+                if (descStatistics.get(switchId) == null
+                        || !(descStatistics.get(switchId).get(0).equals(values
+                                .get(0)))) {
+                    IOFInventoryService inventory = (IOFInventoryService) ServiceHelper
+                            .getInstance(IOFInventoryService.class,
+                                    GlobalConstants.DEFAULT.toString(), this);
+                    if (inventory != null) {
+                        // Notify Inventory Service about the name update
+                        Set<Property> propSet = new HashSet<Property>(1);
+                        Name name = new Name(((OFDescriptionStatistics) values
+                                .get(0)).getSerialNumber());
+                        propSet.add(name);
+                        inventory.updateSwitchProperty(switchId, propSet);
+                    }
+                }
+                // overwrite cache
+                descStatistics.put(switchId, values);
+            } else if (statType == OFStatisticsType.PORT) {
+                // Overwrite cache with new port statistics for this switch
+                portStatistics.put(switchId, values);
+
+                // Wake up the thread which maintains the TX byte counters for each port
+                switchPortStatsUpdated.offer(switchId);
+            }
+        }
+    }
+
+    /*
+     * Generic function to get the statistics form a OF switch
+     */
+    @SuppressWarnings("unchecked")
+    private List<OFStatistics> acquireStatistics(Long switchId,
+            OFStatisticsType statsType, Object target, long timeout) {
+        List<OFStatistics> values = null;
+        String type = null;
+        ISwitch sw = controller.getSwitch(switchId);
+
+        if (sw != null) {
+            OFStatisticsRequest req = new OFStatisticsRequest();
+            req.setStatisticType(statsType);
+            int requestLength = req.getLengthU();
+
+            if (statsType == OFStatisticsType.FLOW) {
+                OFMatch match = null;
+                if (target == null) {
+                    // All flows request
+                    match = new OFMatch();
+                    match.setWildcards(0xffffffff);
+                } else if (!(target instanceof OFMatch)) {
+                    // Malformed request
+                    log.warn("Invalid target type for Flow stats request: "
+                            + target.getClass());
+                    return null;
+                } else {
+                    // Specific flow request
+                    match = (OFMatch) target;
+                }
+                OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest();
+                specificReq.setMatch(match);
+                specificReq.setOutPort(OFPort.OFPP_NONE.getValue());
+                specificReq.setTableId((byte) 0xff);
+                req.setStatistics(Collections
+                        .singletonList((OFStatistics) specificReq));
+                requestLength += specificReq.getLength();
+                type = "FLOW";
+            } else if (statsType == OFStatisticsType.VENDOR) {
+                V6StatsRequest specificReq = new V6StatsRequest();
+                specificReq.setOutPort(OFPort.OFPP_NONE.getValue());
+                specificReq.setTableId((byte) 0xff);
+                req.setStatistics(Collections
+                        .singletonList((OFStatistics) specificReq));
+                requestLength += specificReq.getLength();
+                type = "VENDOR";
+            } else if (statsType == OFStatisticsType.AGGREGATE) {
+                OFAggregateStatisticsRequest specificReq = new OFAggregateStatisticsRequest();
+                OFMatch match = new OFMatch();
+                match.setWildcards(0xffffffff);
+                specificReq.setMatch(match);
+                specificReq.setOutPort(OFPort.OFPP_NONE.getValue());
+                specificReq.setTableId((byte) 0xff);
+                req.setStatistics(Collections
+                        .singletonList((OFStatistics) specificReq));
+                requestLength += specificReq.getLength();
+                type = "AGGREGATE";
+            } else if (statsType == OFStatisticsType.PORT) {
+                short targetPort;
+                if (target == null) {
+                    // All ports request
+                    targetPort = (short) OFPort.OFPP_NONE.getValue();
+                } else if (!(target instanceof Short)) {
+                    // Malformed request
+                    log.warn("Invalid target type for Port stats request: "
+                            + target.getClass());
+                    return null;
+                } else {
+                    // Specific port request
+                    targetPort = (Short) target;
+                }
+                OFPortStatisticsRequest specificReq = new OFPortStatisticsRequest();
+                specificReq.setPortNumber(targetPort);
+                req.setStatistics(Collections
+                        .singletonList((OFStatistics) specificReq));
+                requestLength += specificReq.getLength();
+                type = "PORT";
+            } else if (statsType == OFStatisticsType.QUEUE) {
+                OFQueueStatisticsRequest specificReq = new OFQueueStatisticsRequest();
+                specificReq.setPortNumber((short) OFPort.OFPP_ALL.getValue());
+                specificReq.setQueueId(0xffffffff);
+                req.setStatistics(Collections
+                        .singletonList((OFStatistics) specificReq));
+                requestLength += specificReq.getLength();
+                type = "QUEUE";
+            } else if (statsType == OFStatisticsType.DESC) {
+                type = "DESC";
+            } else if (statsType == OFStatisticsType.TABLE) {
+                type = "TABLE";
+            }
+            req.setLengthU(requestLength);
+            Object result = sw.getStatistics(req);
+
+            if (result == null) {
+                log.warn("Request Timed Out for ({}) from switch {}", type,
+                        HexString.toHexString(switchId));
+            } else if (result instanceof OFError) {
+                log.warn("Switch {} failed to handle ({}) stats request: "
+                        + Utils.getOFErrorString((OFError) result), HexString
+                        .toHexString(switchId), type);
+                if (this.switchSupportsVendorExtStats.get(switchId) == Boolean.TRUE) {
+                    log
+                            .warn(
+                                    "Switching back to regular Flow stats requests for switch {}",
+                                    HexString.toHexString(switchId));
+                    this.switchSupportsVendorExtStats.put(switchId,
+                            Boolean.FALSE);
+                }
+            } else {
+                values = (List<OFStatistics>) result;
+            }
+        }
+        return values;
+    }
+
+    @Override
+    public List<OFStatistics> getOFFlowStatistics(Long switchId) {
+        List<OFStatistics> list = flowStatistics.get(switchId);
+
+        /*
+         *  Check on emptiness as interference between add and get is still
+         *  possible on the inner list (the concurrentMap entry's value)
+         */
+        return (list == null || list.isEmpty()) ? this.dummyList
+                : (list.get(0) instanceof OFVendorStatistics) ? this
+                        .v6StatsListToOFStatsList(list) : list;
+    }
+
+    @Override
+    public List<OFStatistics> getOFFlowStatistics(Long switchId, OFMatch ofMatch) {
+        List<OFStatistics> statsList = flowStatistics.get(switchId);
+
+        /*
+         *  Check on emptiness as interference between add and get is still
+         *  possible on the inner list (the concurrentMap entry's value)
+         */
+        if (statsList == null || statsList.isEmpty()) {
+            return this.dummyList;
+        }
+
+        if (statsList.get(0) instanceof OFVendorStatistics) {
+            /*
+             * Caller could provide regular OF match when we
+             * instead pull the vendor statistics from this node
+             * Caller is not supposed to know whether this switch supports
+             * vendor extensions statistics requests
+             */
+            V6Match targetMatch = (ofMatch instanceof V6Match) ? (V6Match) ofMatch
+                    : new V6Match(ofMatch);
+
+            List<OFStatistics> targetList = v6StatsListToOFStatsList(statsList);
+            for (OFStatistics stats : targetList) {
+                V6StatsReply v6Stats = (V6StatsReply) stats;
+                V6Match v6Match = v6Stats.getMatch();
+                if (v6Match.equals(targetMatch)) {
+                    List<OFStatistics> list = new ArrayList<OFStatistics>();
+                    list.add(stats);
+                    return list;
+                }
+            }
+        } else {
+            for (OFStatistics stats : statsList) {
+                OFFlowStatisticsReply flowStats = (OFFlowStatisticsReply) stats;
+                if (flowStats.getMatch().equals(ofMatch)) {
+                    List<OFStatistics> list = new ArrayList<OFStatistics>();
+                    list.add(stats);
+                    return list;
+                }
+            }
+        }
+        return this.dummyList;
+    }
+
+    /*
+     * Converts the v6 vendor statistics to the OFStatistics
+     */
+    private List<OFStatistics> v6StatsListToOFStatsList(
+            List<OFStatistics> statistics) {
+        List<OFStatistics> v6statistics = new ArrayList<OFStatistics>();
+        if (statistics != null && !statistics.isEmpty()) {
+            for (OFStatistics stats : statistics) {
+                if (stats instanceof OFVendorStatistics) {
+                    List<OFStatistics> r = getV6ReplyStatistics((OFVendorStatistics) stats);
+                    if (r != null) {
+                        v6statistics.addAll(r);
+                    }
+                }
+            }
+        }
+        return v6statistics;
+    }
+
+    private static List<OFStatistics> getV6ReplyStatistics(
+            OFVendorStatistics stat) {
+        int length = stat.getLength();
+        List<OFStatistics> results = new ArrayList<OFStatistics>();
+        if (length < 12)
+            return null; // Nicira Hdr is 12 bytes. We need atleast that much
+        ByteBuffer data = ByteBuffer.allocate(length);
+        stat.writeTo(data);
+        data.rewind();
+        log.trace("getV6ReplyStatistics: Buffer BYTES ARE {}", HexString
+                .toHexString(data.array()));
+
+        int vendor = data.getInt(); //first 4 bytes is vendor id.
+        if (vendor != V6StatsRequest.NICIRA_VENDOR_ID) {
+            log
+                    .debug("Unexpected vendor id: 0x{}", Integer
+                            .toHexString(vendor));
+            return null;
+        } else {
+            //go ahead by 8 bytes which is 8 bytes of 0
+            data.getLong(); //should be all 0's
+            length -= 12; // 4 bytes Nicira Hdr + 8 bytes from above line have been consumed
+        }
+
+        V6StatsReply v6statsreply;
+        int min_len;
+        while (length > 0) {
+            v6statsreply = new V6StatsReply();
+            min_len = v6statsreply.getLength();
+            if (length < v6statsreply.getLength())
+                break;
+            v6statsreply.setActionFactory(stat.getActionFactory());
+            v6statsreply.readFrom(data);
+            if (v6statsreply.getLength() < min_len)
+                break;
+            v6statsreply.setVendorId(vendor);
+            log.trace("V6StatsReply: {}", v6statsreply);
+            length -= v6statsreply.getLength();
+            results.add(v6statsreply);
+        }
+        return results;
+    }
+
+    @Override
+    public List<OFStatistics> queryStatistics(Long switchId,
+            OFStatisticsType statType, Object target, long timeout) {
+        /*
+         * Caller does not know and it is not supposed to know whether
+         * this switch supports vendor extension. We adjust the target for him
+         */
+        if (statType == OFStatisticsType.FLOW) {
+            if (switchSupportsVendorExtStats.get(switchId) == Boolean.TRUE) {
+                statType = OFStatisticsType.VENDOR;
+            }
+        }
+
+        List<OFStatistics> list = this.acquireStatistics(switchId, statType,
+                target, timeout);
+
+        return (list == null) ? null
+                : (statType == OFStatisticsType.VENDOR) ? v6StatsListToOFStatsList(list)
+                        : list;
+    }
+
+    @Override
+    public List<OFStatistics> getOFDescStatistics(Long switchId) {
+        if (!descStatistics.containsKey(switchId))
+            return this.dummyList;
+
+        return descStatistics.get(switchId);
+    }
+
+    @Override
+    public List<OFStatistics> getOFPortStatistics(Long switchId) {
+        if (!portStatistics.containsKey(switchId)) {
+            return this.dummyList;
+        }
+
+        return portStatistics.get(switchId);
+    }
+
+    @Override
+    public List<OFStatistics> getOFPortStatistics(Long switchId, short portId) {
+        if (!portStatistics.containsKey(switchId)) {
+            return this.dummyList;
+        }
+        List<OFStatistics> list = new ArrayList<OFStatistics>(1);
+        for (OFStatistics stats : portStatistics.get(switchId)) {
+            if (((OFPortStatisticsReply) stats).getPortNumber() == portId) {
+                list.add(stats);
+                break;
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public int getFlowsNumber(long switchId) {
+        return this.flowStatistics.get(switchId).size();
+    }
+
+    /*
+     * InventoryShim replay for us all the switch addition which happened before we were brought up
+     */
+    @Override
+    public void updateNode(Node node, UpdateType type, Set<Property> props) {
+        Long switchId = (Long) node.getID();
+        switch (type) {
+        case ADDED:
+            addStatisticsTicks(switchId);
+            break;
+        case REMOVED:
+            clearFlowStatsAndTicks(switchId);
+        default:
+        }
+    }
+
+    @Override
+    public void updateNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props) {
+        // No action
+    }
+
+    /**
+     * Update the cached port rates for this switch with the latest
+     * retrieved port transmit byte count
+     * @param switchId
+     */
+    private synchronized void updatePortsTxRate(long switchId) {
+        List<OFStatistics> newPortStatistics = this.portStatistics
+                .get(switchId);
+        if (newPortStatistics == null) {
+            return;
+        }
+        Map<Short, TxRates> rates = this.txRates.get(switchId);
+        if (rates == null) {
+            // First time rates for this switch are added
+            rates = new HashMap<Short, TxRates>();
+            txRates.put(switchId, rates);
+        }
+        for (OFStatistics stats : newPortStatistics) {
+            OFPortStatisticsReply newPortStat = (OFPortStatisticsReply) stats;
+            short port = newPortStat.getPortNumber();
+            TxRates portRatesHolder = rates.get(port);
+            if (portRatesHolder == null) {
+                // First time rates for this port are added
+                portRatesHolder = new TxRates();
+                rates.put(port, portRatesHolder);
+            }
+            // Get and store the number of transmitted bytes for this port
+            // And handle the case where agent does not support the counter
+            long transmitBytes = newPortStat.getTransmitBytes();
+            long value = (transmitBytes < 0) ? 0 : transmitBytes;
+            portRatesHolder.update(value);
+        }
+    }
+
+    @Override
+    public synchronized long getTransmitRate(Long switchId, Short port) {
+        long average = 0;
+        if (switchId == null || port == null) {
+            return average;
+        }
+        Map<Short, TxRates> perSwitch = txRates.get(switchId);
+        if (perSwitch == null) {
+            return average;
+        }
+        TxRates portRates = perSwitch.get(port);
+        if (portRates == null) {
+            return average;
+        }
+        return portRates.getAverageTxRate();
+    }
+
+    /*
+     * Manual switch name configuration code
+     */
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---OF Switch ID to Name Mapping---\n");
+        help
+                .append("\t ofaddname              - Add a switchId to name mapping entry\n");
+        help
+                .append("\t ofdeletename           - Delete a switchId from the mapping db\n");
+        help.append("\t ofprintnames           - Print the mapping db\n");
+        help.append("---OF Statistics Manager utilities---\n");
+        help
+                .append("\t ofdumpstatsmgr         - Print Internal Stats Mgr db\n");
+        help
+                .append("\t ofstatstimeout         - Change Statistics request's timeout value\n");
+        return help.toString();
+    }
+
+    private boolean isValidSwitchId(String switchId) {
+        String regexDatapathID = "^([0-9a-fA-F]{1,2}[:-]){7}[0-9a-fA-F]{1,2}$";
+        String regexDatapathIDLong = "^[0-9a-fA-F]{1,16}$";
+
+        return (switchId != null && (switchId.matches(regexDatapathID) || switchId
+                .matches(regexDatapathIDLong)));
+    }
+
+    public long getSwitchIDLong(String switchId) {
+        int radix = 16;
+        String switchString = "0";
+
+        if (isValidSwitchId(switchId)) {
+            if (switchId.contains(":")) {
+                // Handle the 00:00:AA:BB:CC:DD:EE:FF notation
+                switchString = switchId.replace(":", "");
+            } else if (switchId.contains("-")) {
+                // Handle the 00-00-AA-BB-CC-DD-EE-FF notation
+                switchString = switchId.replace("-", "");
+            } else {
+                // Handle the 0123456789ABCDEF notation
+                switchString = switchId;
+            }
+        }
+        return Long.parseLong(switchString, radix);
+    }
+
+    public void _ofaddname(CommandInterpreter ci) {
+        if (switchNamesDB == null)
+            switchNamesDB = new HashMap<Long, String>();
+        String switchId = ci.nextArgument();
+        if (!isValidSwitchId(switchId)) {
+            ci.println("Please provide a valid SwithcId");
+            return;
+        }
+        Long sid = getSwitchIDLong(switchId);
+        String switchName = ci.nextArgument();
+        if (switchName == null) {
+            ci.println("Please provide a valid Switch name");
+            return;
+        }
+        switchNamesDB.put(sid, switchName);
+    }
+
+    public void _ofdeletename(CommandInterpreter ci) {
+        if (switchNamesDB == null)
+            return;
+        String switchId = ci.nextArgument();
+        if (!isValidSwitchId(switchId)) {
+            ci.println("Please provide a valid SwitchId");
+            return;
+        }
+        Long sid = getSwitchIDLong(switchId);
+        switchNamesDB.remove(sid);
+    }
+
+    public void _ofprintnames(CommandInterpreter ci) {
+        if (switchNamesDB == null)
+            return;
+        for (Long key : switchNamesDB.keySet()) {
+            ci.println(key + " -> " + switchNamesDB.get(key) + "\n");
+        }
+    }
+
+    /*
+     * Internal information dump code
+     */
+    private String prettyPrintSwitchMap(ConcurrentMap<Long, StatisticsTicks> map) {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append("{");
+        for (Entry<Long, StatisticsTicks> entry : map.entrySet()) {
+            buffer.append(HexString.toHexString(entry.getKey()) + "="
+                    + entry.getValue().toString() + " ");
+        }
+        buffer.append("}");
+        return buffer.toString();
+    }
+
+    public void _ofdumpstatsmgr(CommandInterpreter ci) {
+        ci.println("Global Counter: " + counter);
+        ci
+                .println("Timer Ticks: "
+                        + prettyPrintSwitchMap(statisticsTimerTicks));
+        ci.println("PendingStatsQueue: " + pendingStatsRequests);
+        ci.println("PendingStatsQueue size: " + pendingStatsRequests.size());
+        ci.println("Stats Collector alive: " + statisticsCollector.isAlive());
+        ci.println("Stats Collector State: "
+                + statisticsCollector.getState().toString());
+        ci.println("StatsTimer: " + statisticsTimer.toString());
+        ci.println("Flow Stats Period: " + statisticsTickNumber + " s");
+        ci.println("Desc Stats Period: " + descriptionTickNumber + " s");
+        ci.println("Port Stats Period: " + portTickNumber + " s");
+        ci.println("Stats request timeout: " + Float.valueOf(statisticsTimeout)
+                / 1000.0 + " s");
+    }
+
+    public void _ofstatstimeout(CommandInterpreter ci) {
+        String timeoutString = ci.nextArgument();
+        if (timeoutString == null || !timeoutString.matches("^[0-9]+$")) {
+            ci.println("Invalid value. Has to be numeric.");
+            return;
+        }
+
+        long newTimeout = Long.valueOf(timeoutString);
+        if (newTimeout < 50 || newTimeout > 60000) {
+            ci.println("Invalid value. Valid range is [50-60000]ms");
+            return;
+        }
+        this.statisticsTimeout = newTimeout;
+        ci.println("New value: " + statisticsTimeout + " ms");
+    }
+
+    public void _resetSwitchCapability(CommandInterpreter ci) {
+        String sidString = ci.nextArgument();
+        Long sid = null;
+        if (sidString == null) {
+            ci.println("Insert the switch id (numeric value)");
+            return;
+        }
+        try {
+            sid = Long.valueOf(sidString);
+            this.switchSupportsVendorExtStats.put(sid, Boolean.TRUE);
+            ci.println("Vendor capability for switch " + sid + " set to "
+                    + this.switchSupportsVendorExtStats.get(sid));
+        } catch (NumberFormatException e) {
+            ci.println("Invalid switch id. Has to be numeric.");
+        }
+
+    }
+
+    public void _ofbw(CommandInterpreter ci) {
+        String sidString = ci.nextArgument();
+        Long sid = null;
+        if (sidString == null) {
+            ci.println("Insert the switch id (numeric value)");
+            return;
+        }
+        try {
+            sid = Long.valueOf(sidString);
+        } catch (NumberFormatException e) {
+            ci.println("Invalid switch id. Has to be numeric.");
+        }
+        if (sid != null) {
+            Map<Short, TxRates> thisSwitchRates = txRates.get(sid);
+            ci.println("Bandwidth utilization (" + factoredSamples
+                    * portTickNumber + " sec average) for switch "
+                    + HexEncode.longToHexString(sid) + ":");
+            if (thisSwitchRates == null) {
+                ci.println("Not available");
+            } else {
+                for (Entry<Short, TxRates> entry : thisSwitchRates.entrySet()) {
+                    ci.println("Port: " + entry.getKey() + ": "
+                            + entry.getValue().getAverageTxRate() + " bps");
+                }
+            }
+        }
+    }
+
+    public void _txratewindow(CommandInterpreter ci) {
+        String averageWindow = ci.nextArgument();
+        short seconds = 0;
+        if (averageWindow == null) {
+            ci
+                    .println("Insert the length in seconds of the average window for tx rate");
+            ci
+                    .println("Current: " + factoredSamples * portTickNumber
+                            + " secs");
+            return;
+        }
+        try {
+            seconds = Short.valueOf(averageWindow);
+        } catch (NumberFormatException e) {
+            ci.println("Invalid period.");
+        }
+        OFStatisticsManager.factoredSamples = (short) (seconds / portTickNumber);
+        ci.println("New: " + factoredSamples * portTickNumber + " secs");
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/PortConverter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/PortConverter.java
new file mode 100644 (file)
index 0000000..8de7693
--- /dev/null
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import org.openflow.protocol.OFPort;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+
+/**
+ * Abstract class which provides the utilities for converting
+ * the Openflow port number to the equivalent NodeConnector and vice versa
+ *
+ *
+ *
+ */
+public abstract class PortConverter {
+    private static final int maxOFPhysicalPort = (OFPort.OFPP_MAX.getValue() & 0x7FFF) | 0x8000;
+
+    /**
+     * Converts the Openflow port number to the equivalent NodeConnector.
+     */
+    public static NodeConnector toNodeConnector(short ofPort, Node node) {
+        // Restore original OF unsigned 16 bits value for the comparison
+        int unsignedOFPort = (ofPort & 0x7FFF) | 0x8000;
+
+        if (unsignedOFPort > maxOFPhysicalPort) {
+            if (ofPort == OFPort.OFPP_LOCAL.getValue()) {
+                return NodeConnectorCreator.createNodeConnector(
+                        NodeConnectorIDType.SWSTACK,
+                        NodeConnector.SPECIALNODECONNECTORID, node);
+            } else if (ofPort == OFPort.OFPP_NORMAL.getValue()) {
+                return NodeConnectorCreator.createNodeConnector(
+                        NodeConnectorIDType.HWPATH,
+                        NodeConnector.SPECIALNODECONNECTORID, node);
+            } else if (ofPort == OFPort.OFPP_CONTROLLER.getValue()) {
+                return NodeConnectorCreator.createNodeConnector(
+                        NodeConnectorIDType.CONTROLLER,
+                        NodeConnector.SPECIALNODECONNECTORID, node);
+            }
+        }
+        return NodeConnectorCreator.createNodeConnector(ofPort, node);
+    }
+
+    /**
+     * Converts the NodeConnector to the equivalent Openflow port number
+     */
+    public static short toOFPort(NodeConnector salPort) {
+        if (salPort.getType().equals(NodeConnectorIDType.SWSTACK)) {
+            return OFPort.OFPP_LOCAL.getValue();
+        } else if (salPort.getType().equals(
+                NodeConnectorIDType.HWPATH)) {
+            return OFPort.OFPP_NORMAL.getValue();
+        } else if (salPort.getType().equals(
+                NodeConnectorIDType.CONTROLLER)) {
+            return OFPort.OFPP_CONTROLLER.getValue();
+        }
+        return (Short) salPort.getID();
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/PortStatisticsConverter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/PortStatisticsConverter.java
new file mode 100644 (file)
index 0000000..2b4df3f
--- /dev/null
@@ -0,0 +1,76 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openflow.protocol.statistics.OFPortStatisticsReply;
+import org.openflow.protocol.statistics.OFStatistics;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+/**
+ * Converts an openflow list of port statistics in a SAL list of
+ * NodeConnectorStatistics objects
+ *
+ *
+ *
+ */
+public class PortStatisticsConverter {
+    private long switchId;
+    private List<OFStatistics> ofStatsList;
+    private List<NodeConnectorStatistics> ncStatsList;
+
+    public PortStatisticsConverter(long switchId, List<OFStatistics> statsList) {
+        this.switchId = switchId;
+        if (statsList == null || statsList.isEmpty()) {
+            this.ofStatsList = new ArrayList<OFStatistics>(1); // dummy list
+        } else {
+            this.ofStatsList = new ArrayList<OFStatistics>(statsList);
+        }
+        this.ncStatsList = null;
+    }
+
+    public List<NodeConnectorStatistics> getNodeConnectorStatsList() {
+        if (this.ofStatsList != null && this.ncStatsList == null) {
+            this.ncStatsList = new ArrayList<NodeConnectorStatistics>();
+            OFPortStatisticsReply ofPortStat;
+            Node node = NodeCreator.createOFNode(switchId);
+            for (OFStatistics ofStat : this.ofStatsList) {
+                ofPortStat = (OFPortStatisticsReply) ofStat;
+                NodeConnectorStatistics NCStat = new NodeConnectorStatistics();
+                NCStat.setNodeConnector(PortConverter.toNodeConnector(
+                        ofPortStat.getPortNumber(), node));
+                NCStat.setReceivePacketCount(ofPortStat.getreceivePackets());
+                NCStat.setTransmitPacketCount(ofPortStat.getTransmitPackets());
+                NCStat.setReceiveByteCount(ofPortStat.getReceiveBytes());
+                NCStat.setTransmitByteCount(ofPortStat.getTransmitBytes());
+                NCStat.setReceiveDropCount(ofPortStat.getReceiveDropped());
+                NCStat.setTransmitDropCount(ofPortStat.getTransmitDropped());
+                NCStat.setReceiveErrorCount(ofPortStat.getreceiveErrors());
+                NCStat.setTransmitErrorCount(ofPortStat.getTransmitErrors());
+                NCStat.setReceiveFrameErrorCount(ofPortStat
+                        .getReceiveFrameErrors());
+                NCStat.setReceiveOverRunErrorCount(ofPortStat
+                        .getReceiveOverrunErrors());
+                NCStat
+                        .setReceiveCRCErrorCount(ofPortStat
+                                .getReceiveCRCErrors());
+                NCStat.setCollisionCount(ofPortStat.getCollisions());
+                this.ncStatsList.add(NCStat);
+            }
+        }
+        return this.ncStatsList;
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/ReadService.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/ReadService.java
new file mode 100644 (file)
index 0000000..9ff3d21
--- /dev/null
@@ -0,0 +1,149 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.Dictionary;
+import java.util.List;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.protocol_plugin.openflow.IPluginReadServiceFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Node.NodeIDType;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.IPluginInReadService;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.reader.NodeDescription;
+
+/**
+ * Container Instance of IPluginInReadService implementation class
+ *
+ *
+ *
+ */
+public class ReadService implements IPluginInReadService {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ReadService.class);
+    private IPluginReadServiceFilter filter;
+    private String containerName;
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    @SuppressWarnings("unchecked")
+    void init(Component c) {
+        Dictionary<Object, Object> props = c.getServiceProperties();
+        containerName = (props != null) ? (String) props.get("containerName")
+                : null;
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    public void setService(IPluginReadServiceFilter filter) {
+        this.filter = filter;
+    }
+
+    public void unsetService(IPluginReadServiceFilter filter) {
+        this.filter = null;
+    }
+
+    @Override
+    public FlowOnNode readFlow(Node node, Flow flow, boolean cached) {
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            logger.error("Invalid node type");
+            return null;
+        }
+
+        return filter.readFlow(containerName, node, flow, cached);
+    }
+
+    @Override
+    public List<FlowOnNode> readAllFlow(Node node, boolean cached) {
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            logger.error("Invalid node type");
+            return null;
+        }
+
+        return filter.readAllFlow(containerName, node, cached);
+    }
+
+    @Override
+    public NodeDescription readDescription(Node node, boolean cached) {
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            logger.error("Invalid node type");
+            return null;
+        }
+
+        return filter.readDescription(node, cached);
+    }
+
+    @Override
+    public NodeConnectorStatistics readNodeConnector(NodeConnector connector,
+            boolean cached) {
+        if (!connector.getNode().getType()
+            .equals(NodeIDType.OPENFLOW)) {
+            logger.error("Invalid node type");
+            return null;
+        }
+        return filter.readNodeConnector(containerName, connector, cached);
+    }
+
+    @Override
+    public List<NodeConnectorStatistics> readAllNodeConnector(Node node,
+            boolean cached) {
+        if (!node.getType().equals(NodeIDType.OPENFLOW)) {
+            logger.error("Invalid node type");
+            return null;
+        }
+
+        return filter.readAllNodeConnector(containerName, node, cached);
+    }
+
+    @Override
+    public long getTransmitRate(NodeConnector connector) {
+        if (!connector.getNode().getType()
+            .equals(NodeIDType.OPENFLOW)) {
+            logger.error("Invalid node type");
+            return 0;
+        }
+        return filter.getTransmitRate(containerName, connector);
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/ReadServiceFilter.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/ReadServiceFilter.java
new file mode 100644 (file)
index 0000000..4301bfd
--- /dev/null
@@ -0,0 +1,421 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsManager;
+import org.opendaylight.controller.protocol_plugin.openflow.IPluginReadServiceFilter;
+import org.opendaylight.controller.protocol_plugin.openflow.core.IController;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.statistics.OFPortStatisticsReply;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.ActionType;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.reader.NodeDescription;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+/**
+ * Read Service shim layer which is in charge of filtering the flow statistics
+ * based on container. It is a Global instance.
+ *
+ *
+ *
+ */
+public class ReadServiceFilter implements IPluginReadServiceFilter,
+        IContainerListener {
+    private static final Logger logger = LoggerFactory
+            .getLogger(ReadServiceFilter.class);
+    private IController controller = null;
+    private IOFStatisticsManager statsMgr = null;
+    private Map<String, Set<NodeConnector>> containerToNc;
+
+    public void setController(IController core) {
+        this.controller = core;
+    }
+
+    public void unsetController(IController core) {
+        if (this.controller == core) {
+            this.controller = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        containerToNc = new HashMap<String, Set<NodeConnector>>();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    public void setService(IOFStatisticsManager service) {
+        this.statsMgr = service;
+    }
+
+    public void unsetService(IOFStatisticsManager service) {
+        this.statsMgr = null;
+    }
+
+    @Override
+    public FlowOnNode readFlow(String container, Node node, Flow flow,
+            boolean cached) {
+
+        if (controller == null) {
+            // Avoid to provide cached statistics if controller went down.
+            // They are not valid anymore anyway
+            logger.error("Internal plugin error");
+            return null;
+        }
+
+        long sid = (Long) node.getID();
+        OFMatch ofMatch = new FlowConverter(flow).getOFMatch();
+        List<OFStatistics> ofList = (cached == true) ? statsMgr
+                .getOFFlowStatistics(sid, ofMatch) : statsMgr.queryStatistics(
+                sid, OFStatisticsType.FLOW, ofMatch, 6000);
+
+        /*
+         * Convert and filter the statistics per container
+         */
+        List<FlowOnNode> flowOnNodeList = new FlowStatisticsConverter(ofList)
+                .getFlowOnNodeList(node);
+        List<FlowOnNode> filteredList = filterFlowListPerContainer(container,
+                node, flowOnNodeList);
+
+        return (filteredList == null || filteredList.isEmpty()) ? null
+                : filteredList.get(0);
+    }
+
+    @Override
+    public List<FlowOnNode> readAllFlow(String container, Node node,
+            boolean cached) {
+
+        long sid = (Long) node.getID();
+        List<OFStatistics> ofList = (cached == true) ? statsMgr
+                .getOFFlowStatistics(sid) : statsMgr.queryStatistics(sid,
+                OFStatisticsType.FLOW, null, 6000);
+
+        /*
+         * Convert and filter the statistics per container
+         */
+        List<FlowOnNode> flowOnNodeList = new FlowStatisticsConverter(ofList)
+                .getFlowOnNodeList(node);
+        List<FlowOnNode> filteredList = filterFlowListPerContainer(container,
+                node, flowOnNodeList);
+
+        return (filteredList == null) ? null : filteredList;
+
+    }
+
+    @Override
+    public NodeDescription readDescription(Node node, boolean cached) {
+
+        if (controller == null) {
+            logger.error("Internal plugin error");
+            return null;
+        }
+
+        long sid = (Long) node.getID();
+        List<OFStatistics> ofList = (cached == true) ? statsMgr
+                .getOFDescStatistics(sid) : statsMgr.queryStatistics(sid,
+                OFStatisticsType.DESC, null, 6000);
+
+        return new DescStatisticsConverter(ofList).getHwDescription();
+    }
+
+    /**
+     * Filters a list of FlowOnNode elements based on the container
+     *
+     * @param container
+     * @param nodeId
+     * @param list
+     * @return
+     */
+    public List<FlowOnNode> filterFlowListPerContainer(String container,
+            Node nodeId, List<FlowOnNode> list) {
+        if (list == null) {
+            return null;
+        }
+
+        // Create new filtered list of flows
+        List<FlowOnNode> newList = new ArrayList<FlowOnNode>();
+
+        for (FlowOnNode target : list) {
+            // Check whether the described flow (match + actions) belongs to this container
+            if (flowBelongToContainer(container, nodeId, target.getFlow())) {
+                newList.add(target);
+            }
+        }
+
+        return newList;
+    }
+
+    /**
+     * Filters a list of FlowOnNode elements based on the container
+     *
+     * @param container
+     * @param nodeId
+     * @param list
+     * @return
+     */
+    public List<OFStatistics> filterPortListPerContainer(String container,
+            long switchId, List<OFStatistics> list) {
+        if (list == null) {
+            return null;
+        }
+
+        // Create new filtered list of flows
+        List<OFStatistics> newList = new ArrayList<OFStatistics>();
+
+        for (OFStatistics stat : list) {
+            OFPortStatisticsReply target = (OFPortStatisticsReply) stat;
+            NodeConnector nc = NodeConnectorCreator.createOFNodeConnector(
+                    target.getPortNumber(), NodeCreator.createOFNode(switchId));
+            if (containerOwnsNodeConnector(container, nc)) {
+                newList.add(target);
+            }
+        }
+
+        return newList;
+    }
+
+    /**
+     * Returns whether the specified flow (flow match + actions)
+     * belongs to the container
+     *
+     * @param container
+     * @param node
+     * @param flow
+     * @return true if it belongs
+     */
+    public boolean flowBelongToContainer(String container, Node node, Flow flow) {
+        // All flows belong to the default container
+        if (container.equals(GlobalConstants.DEFAULT.toString())) {
+            return true;
+        }
+        return (flowPortsBelongToContainer(container, node, flow)
+                && flowVlanBelongsToContainer(container, node, flow) && flowSpecAllowsFlow(
+                container, flow.getMatch()));
+    }
+
+    /**
+     * Returns whether the passed NodeConnector belongs to the container
+     *
+     * @param container        container name
+     * @param p                node connector to test
+     * @return                 true if belongs false otherwise
+     */
+    public boolean containerOwnsNodeConnector(String container, NodeConnector p) {
+        // All node connectors belong to the default container
+        if (container.equals(GlobalConstants.DEFAULT.toString())) {
+            return true;
+        }
+        Set<NodeConnector> portSet = containerToNc.get(container);
+        return (portSet == null) ? false : portSet.contains(p);
+    }
+
+    /**
+     * Returns whether the container flowspec allows the passed flow
+     *
+     * @param container
+     * @param match
+     * @return
+     */
+    private boolean flowSpecAllowsFlow(String container, Match match) {
+        return true; // Always true for now
+    }
+
+    /**
+     * Check whether the vlan field in the flow match is the same
+     * of the static vlan configured for the container
+     *
+     * @param container
+     * @param node
+     * @param flow
+     * @return
+     */
+    private boolean flowVlanBelongsToContainer(String container, Node node,
+            Flow flow) {
+        return true; // Always true for now
+    }
+
+    /**
+     * Check whether the ports in the flow match and flow actions for
+     * the specified node belong to the container
+     *
+     * @param container
+     * @param node
+     * @param flow
+     * @return
+     */
+    private boolean flowPortsBelongToContainer(String container, Node node,
+            Flow flow) {
+        Match m = flow.getMatch();
+        if (m.isPresent(MatchType.IN_PORT)) {
+            NodeConnector inPort = (NodeConnector) m
+                    .getField(MatchType.IN_PORT).getValue();
+
+            // If the incoming port is specified, check if it belongs to
+            if (!containerOwnsNodeConnector(container, inPort)) {
+                return false;
+            }
+        }
+
+        // If an outgoing port is specified, it must belong to this container
+        for (Action action : flow.getActions()) {
+            if (action.getType() == ActionType.OUTPUT) {
+                NodeConnector outPort = (NodeConnector) ((Output) action)
+                        .getPort();
+                if (!containerOwnsNodeConnector(container, outPort)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void containerFlowUpdated(String containerName,
+            ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) {
+
+    }
+
+    @Override
+    public void nodeConnectorUpdated(String containerName, NodeConnector p,
+            UpdateType type) {
+        Set<NodeConnector> target = null;
+
+        switch (type) {
+        case ADDED:
+            if (!containerToNc.containsKey(containerName)) {
+                containerToNc.put(containerName, new HashSet<NodeConnector>());
+            }
+            containerToNc.get(containerName).add(p);
+            break;
+        case CHANGED:
+            break;
+        case REMOVED:
+            target = containerToNc.get(containerName);
+            if (target != null) {
+                target.remove(p);
+            }
+            break;
+        default:
+        }
+    }
+
+    @Override
+    public void tagUpdated(String containerName, Node n, short oldTag,
+            short newTag, UpdateType t) {
+        // Not interested in this event
+    }
+
+    @Override
+    public void containerModeUpdated(UpdateType t) {
+        // do nothing
+    }
+
+    @Override
+    public NodeConnectorStatistics readNodeConnector(String containerName,
+            NodeConnector connector, boolean cached) {
+        if (!containerOwnsNodeConnector(containerName, connector)) {
+            return null;
+        }
+        Node node = connector.getNode();
+        long sid = (Long) node.getID();
+        short portId = (Short) connector.getID();
+        List<OFStatistics> ofList = (cached == true) ? statsMgr
+                .getOFPortStatistics(sid, portId) : statsMgr.queryStatistics(
+                sid, OFStatisticsType.PORT, portId, 6000);
+
+        List<NodeConnectorStatistics> ncStatistics = new PortStatisticsConverter(
+                sid, ofList).getNodeConnectorStatsList();
+        return (ncStatistics.isEmpty()) ? new NodeConnectorStatistics()
+                : ncStatistics.get(0);
+    }
+
+    @Override
+    public List<NodeConnectorStatistics> readAllNodeConnector(
+            String containerName, Node node, boolean cached) {
+
+        long sid = (Long) node.getID();
+        List<OFStatistics> ofList = (cached == true) ? statsMgr
+                .getOFPortStatistics(sid) : statsMgr.queryStatistics(sid,
+                OFStatisticsType.FLOW, null, 6000);
+
+        List<OFStatistics> filteredList = filterPortListPerContainer(
+                containerName, sid, ofList);
+
+        return new PortStatisticsConverter(sid, filteredList)
+                .getNodeConnectorStatsList();
+    }
+
+    @Override
+    public long getTransmitRate(String containerName, NodeConnector connector) {
+        if (!containerOwnsNodeConnector(containerName, connector)) {
+            return 0;
+        }
+
+        long switchId = (Long) connector.getNode().getID();
+        short port = (Short) connector.getID();
+
+        return statsMgr.getTransmitRate(switchId, port);
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/TopologyServiceShim.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/TopologyServiceShim.java
new file mode 100644 (file)
index 0000000..b62b068
--- /dev/null
@@ -0,0 +1,645 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.IOFStatisticsManager;
+import org.opendaylight.controller.protocol_plugin.openflow.IRefreshInternalProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.ITopologyServiceShimListener;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.ContainerFlow;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.IContainerListener;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.discovery.IDiscoveryService;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+
+/**
+ * The class describes a shim layer that relays the topology events from OpenFlow
+ * core to various listeners. The notifications are filtered based on container
+ * configurations.
+ */
+public class TopologyServiceShim implements IDiscoveryService,
+        IContainerListener, CommandProvider, IRefreshInternalProvider {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(TopologyServiceShim.class);
+    private ConcurrentMap<String, ITopologyServiceShimListener> topologyServiceShimListeners = new ConcurrentHashMap<String, ITopologyServiceShimListener>();
+    private ConcurrentMap<NodeConnector, List<String>> containerMap = new ConcurrentHashMap<NodeConnector, List<String>>();
+    private ConcurrentMap<String, Map<NodeConnector, Pair<Edge, Set<Property>>>> edgeMap = new ConcurrentHashMap<String, Map<NodeConnector, Pair<Edge, Set<Property>>>>();
+
+    private BlockingQueue<NotifyEntry> notifyQ;
+    private Thread notifyThread;
+    private BlockingQueue<String> bulkNotifyQ;
+    private Thread ofPluginTopoBulkUpdate;
+    private volatile Boolean shuttingDown = false;
+    private IOFStatisticsManager statsMgr;
+    private Timer pollTimer;
+    private TimerTask txRatePoller;
+    private Thread bwUtilNotifyThread;
+    private BlockingQueue<UtilizationUpdate> bwUtilNotifyQ;
+    private List<NodeConnector> connectorsOverUtilized;
+    private float bwThresholdFactor = (float) 0.8; // Threshold = 80% of link bandwidth
+
+    class NotifyEntry {
+        String container;
+        Pair<Edge, Set<Property>> edgeProps;
+        UpdateType type;
+
+        NotifyEntry(String container, Pair<Edge, Set<Property>> edgeProps,
+                UpdateType type) {
+            this.container = container;
+            this.edgeProps = edgeProps;
+            this.type = type;
+        }
+    }
+
+    class TopologyNotify implements Runnable {
+        private final BlockingQueue<NotifyEntry> notifyQ;
+
+        TopologyNotify(BlockingQueue<NotifyEntry> notifyQ) {
+            this.notifyQ = notifyQ;
+        }
+
+        public void run() {
+            while (true) {
+                try {
+                    NotifyEntry entry = notifyQ.take();
+
+                    ITopologyServiceShimListener topologServiceShimListener = topologyServiceShimListeners
+                            .get(entry.container);
+                    topologServiceShimListener.edgeUpdate(entry.edgeProps
+                            .getLeft(), entry.type, entry.edgeProps.getRight());
+
+                    entry = null;
+                } catch (InterruptedException e1) {
+                    logger.warn("TopologyNotify interrupted", e1.getMessage());
+                    if (shuttingDown) {
+                        return;
+                    }
+                } catch (Exception e2) {
+                    e2.printStackTrace();
+                }
+            }
+        }
+    }
+
+    class UtilizationUpdate {
+        NodeConnector connector;
+        UpdateType type;
+
+        UtilizationUpdate(NodeConnector connector, UpdateType type) {
+            this.connector = connector;
+            this.type = type;
+        }
+    }
+
+    class BwUtilizationNotify implements Runnable {
+        private final BlockingQueue<UtilizationUpdate> notifyQ;
+
+        BwUtilizationNotify(BlockingQueue<UtilizationUpdate> notifyQ) {
+            this.notifyQ = notifyQ;
+        }
+
+        public void run() {
+            while (true) {
+                try {
+                    UtilizationUpdate update = notifyQ.take();
+                    NodeConnector connector = update.connector;
+                    Set<String> containerList = edgeMap.keySet();//.get(connector);
+                    for (String container : containerList) {
+                        Map<NodeConnector, Pair<Edge, Set<Property>>> edgePropsMap = edgeMap
+                                .get(container);
+                        Edge edge = edgePropsMap.get(connector).getLeft();
+                        if (edge.getTailNodeConnector().equals(connector)) {
+                            ITopologyServiceShimListener topologServiceShimListener = topologyServiceShimListeners
+                                    .get(container);
+                            if (update.type == UpdateType.ADDED) {
+                                topologServiceShimListener
+                                        .edgeOverUtilized(edge);
+                            } else {
+                                topologServiceShimListener
+                                        .edgeUtilBackToNormal(edge);
+                            }
+                        }
+                    }
+                } catch (InterruptedException e1) {
+                    logger
+                            .warn(
+                                    "Edge Bandwidth Utilization Notify Thread interrupted",
+                                    e1.getMessage());
+                    if (shuttingDown) {
+                        return;
+                    }
+                } catch (Exception e2) {
+                    e2.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        logger.trace("Init called");
+        connectorsOverUtilized = new ArrayList<NodeConnector>();
+        notifyQ = new LinkedBlockingQueue<NotifyEntry>();
+        notifyThread = new Thread(new TopologyNotify(notifyQ));
+        bwUtilNotifyQ = new LinkedBlockingQueue<UtilizationUpdate>();
+        bwUtilNotifyThread = new Thread(new BwUtilizationNotify(bwUtilNotifyQ));
+        bulkNotifyQ = new LinkedBlockingQueue<String>();
+        ofPluginTopoBulkUpdate = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        String containerName = bulkNotifyQ.take();
+                        logger.debug("Bulk Notify container:{}", containerName);
+                        TopologyBulkUpdate(containerName);
+                    } catch (InterruptedException e) {
+                        logger.warn("Topology Bulk update thread interrupted");
+                        if (shuttingDown) {
+                            return;
+                        }
+                    }
+                }
+            }
+        }, "Topology Bulk Update");
+
+        // Initialize node connector tx bit rate poller timer
+        pollTimer = new Timer();
+        txRatePoller = new TimerTask() {
+            @Override
+            public void run() {
+                pollTxBitRates();
+            }
+        };
+
+        registerWithOSGIConsole();
+    }
+
+    /**
+     * Continuously polls the transmit bit rate for all the node connectors
+     * from statistics manager and trigger the warning notification upward
+     * when the transmit rate is above a threshold which is a percentage of
+     * the edge bandwidth
+     */
+    protected void pollTxBitRates() {
+        Map<NodeConnector, Pair<Edge, Set<Property>>> globalContainerEdges = edgeMap
+                .get(GlobalConstants.DEFAULT.toString());
+        if (globalContainerEdges == null) {
+            return;
+        }
+
+        for (NodeConnector connector : globalContainerEdges.keySet()) {
+            // Skip if node connector belongs to production switch
+            if (connector.getType() == NodeConnector.NodeConnectorIDType.PRODUCTION) {
+                continue;
+            }
+
+            // Get edge for which this node connector is head
+            Pair<Edge, Set<Property>> props = this.edgeMap.get(
+                    GlobalConstants.DEFAULT.toString()).get(connector);
+            // On switch mgr restart the props get reset
+            if (props == null) {
+                continue;
+            }
+            Set<Property> propSet = props.getRight();
+            if (propSet == null) {
+                continue;
+            }
+
+            float bw = 0;
+            for (Property prop : propSet) {
+                if (prop instanceof Bandwidth) {
+                    bw = ((Bandwidth) prop).getValue();
+                    break;
+                }
+            }
+
+            // Skip if agent did not provide a bandwidth info for the edge
+            if (bw == 0) {
+                continue;
+            }
+
+            // Compare bandwidth usage
+            Long switchId = (Long) connector.getNode().getID();
+            Short port = (Short) connector.getID();
+            float rate = statsMgr.getTransmitRate(switchId, port);
+            if (rate > bwThresholdFactor * bw) {
+                if (!connectorsOverUtilized.contains(connector)) {
+                    connectorsOverUtilized.add(connector);
+                    this.bwUtilNotifyQ.add(new UtilizationUpdate(connector,
+                            UpdateType.ADDED));
+                }
+            } else {
+                if (connectorsOverUtilized.contains(connector)) {
+                    connectorsOverUtilized.remove(connector);
+                    this.bwUtilNotifyQ.add(new UtilizationUpdate(connector,
+                            UpdateType.REMOVED));
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        logger.trace("DESTROY called!");
+        notifyQ = null;
+        notifyThread = null;
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+        logger.trace("START called!");
+        notifyThread.start();
+        bwUtilNotifyThread.start();
+        ofPluginTopoBulkUpdate.start();
+        pollTimer.scheduleAtFixedRate(txRatePoller, 10000, 5000);
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+        logger.trace("STOP called!");
+        shuttingDown = true;
+        notifyThread.interrupt();
+    }
+
+    void setTopologyServiceShimListener(Map<?, ?> props,
+            ITopologyServiceShimListener s) {
+        if (props == null) {
+            logger.error("Didn't receive the service properties");
+            return;
+        }
+        String containerName = (String) props.get("containerName");
+        if (containerName == null) {
+            logger.error("containerName not supplied");
+            return;
+        }
+        if ((this.topologyServiceShimListeners != null)
+                && !this.topologyServiceShimListeners.containsKey(s)) {
+            this.topologyServiceShimListeners.put(containerName, s);
+            logger.trace("Added topologyServiceShimListener for container:"
+                    + containerName);
+        }
+    }
+
+    void unsetTopologyServiceShimListener(Map<?, ?> props,
+            ITopologyServiceShimListener s) {
+        if (props == null) {
+            logger.error("Didn't receive the service properties");
+            return;
+        }
+        String containerName = (String) props.get("containerName");
+        if (containerName == null) {
+            logger.error("containerName not supplied");
+            return;
+        }
+        if ((this.topologyServiceShimListeners != null)
+                && this.topologyServiceShimListeners.containsKey(s)) {
+            this.topologyServiceShimListeners.remove(containerName);
+            logger.trace("Removed topologyServiceShimListener for container: "
+                    + containerName);
+        }
+    }
+
+    void setStatisticsManager(IOFStatisticsManager s) {
+        this.statsMgr = s;
+    }
+
+    void unsetStatisticsManager(IOFStatisticsManager s) {
+        if (this.statsMgr == s) {
+            this.statsMgr = null;
+        }
+    }
+
+    private void removeNodeConnector(String container,
+            NodeConnector nodeConnector) {
+        Map<NodeConnector, Pair<Edge, Set<Property>>> edgePropsMap = edgeMap
+                .get(container);
+        if (edgePropsMap == null) {
+            return;
+        }
+
+        // Remove edge in one direction
+        Pair<Edge, Set<Property>> edgeProps = edgePropsMap.get(nodeConnector);
+        if (edgeProps == null) {
+            return;
+        }
+        notifyEdge(container, edgeProps.getLeft(), UpdateType.REMOVED, null);
+
+        // Remove edge in another direction
+        edgeProps = edgePropsMap
+                .get(edgeProps.getLeft().getHeadNodeConnector());
+        if (edgeProps == null) {
+            return;
+        }
+        notifyEdge(container, edgeProps.getLeft(), UpdateType.REMOVED, null);
+    }
+
+    private void notifyEdge(String container, Edge edge, UpdateType type,
+            Set<Property> props) {
+        Map<NodeConnector, Pair<Edge, Set<Property>>> edgePropsMap = edgeMap
+                .get(container);
+        NodeConnector src = edge.getTailNodeConnector();
+        Pair<Edge, Set<Property>> edgeProps = new ImmutablePair<Edge, Set<Property>>(
+                edge, props);
+
+        switch (type) {
+        case ADDED:
+        case CHANGED:
+            if (edgePropsMap == null) {
+                edgePropsMap = new HashMap<NodeConnector, Pair<Edge, Set<Property>>>();
+            } else {
+                if (edgePropsMap.containsKey(src)
+                        && edgePropsMap.get(src).equals(edgeProps)) {
+                    // Entry already exists. Return here.
+                    return;
+                }
+            }
+            edgePropsMap.put(src, edgeProps);
+            edgeMap.put(container, edgePropsMap);
+            break;
+        case REMOVED:
+            if (edgePropsMap != null) {
+                edgePropsMap.remove(src);
+                if (edgePropsMap.isEmpty()) {
+                    edgeMap.remove(container);
+                } else {
+                    edgeMap.put(container, edgePropsMap);
+                }
+            }
+            break;
+        default:
+            logger.debug("Invalid " + type + " Edge " + edge
+                    + " in container {}", container);
+            return;
+        }
+
+        notifyQ.add(new NotifyEntry(container, edgeProps, type));
+
+        logger.trace(type + " Edge " + edge + " in container {}", container);
+    }
+
+    @Override
+    public void notifyEdge(Edge edge, UpdateType type, Set<Property> props) {
+        if ((edge == null) || (type == null)) {
+            return;
+        }
+
+        // Notify default container
+        notifyEdge(GlobalConstants.DEFAULT.toString(), edge, type, props);
+
+        // Notify the corresponding containers
+        List<String> containers = getEdgeContainers(edge);
+        if (containers != null) {
+            for (String container : containers) {
+                notifyEdge(container, edge, type, props);
+            }
+        }
+    }
+
+    /*
+     * Return a list of containers the edge associated with
+     */
+    private List<String> getEdgeContainers(Edge edge) {
+        NodeConnector src = edge.getTailNodeConnector(), dst = edge
+                .getHeadNodeConnector();
+
+        if (!src.getType().equals(
+                NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+            /* Find the common containers for both ends */
+            List<String> srcContainers = this.containerMap.get(src), dstContainers = this.containerMap
+                    .get(dst), cmnContainers = null;
+            if ((srcContainers != null) && (dstContainers != null)) {
+                cmnContainers = new ArrayList<String>(srcContainers);
+                cmnContainers.retainAll(dstContainers);
+            }
+            return cmnContainers;
+        } else {
+            /*
+             * If the neighbor is part of a monitored production network, get
+             * the containers that the edge port belongs to
+             */
+            return this.containerMap.get(dst);
+        }
+    }
+
+    @Override
+    public void tagUpdated(String containerName, Node n, short oldTag,
+            short newTag, UpdateType t) {
+    }
+
+    @Override
+    public void containerFlowUpdated(String containerName,
+            ContainerFlow previousFlow, ContainerFlow currentFlow, UpdateType t) {
+    }
+
+    @Override
+    public void nodeConnectorUpdated(String containerName, NodeConnector p,
+            UpdateType t) {
+        if (this.containerMap == null) {
+            logger.error("containerMap is NULL");
+            return;
+        }
+        List<String> containers = this.containerMap.get(p);
+        if (containers == null) {
+            containers = new CopyOnWriteArrayList<String>();
+        }
+        boolean updateMap = false;
+        switch (t) {
+        case ADDED:
+            if (!containers.contains(containerName)) {
+                containers.add(containerName);
+                updateMap = true;
+            }
+            break;
+        case REMOVED:
+            if (containers.contains(containerName)) {
+                containers.remove(containerName);
+                updateMap = true;
+                removeNodeConnector(containerName, p);
+            }
+            break;
+        case CHANGED:
+            break;
+        }
+        if (updateMap) {
+            if (containers.isEmpty()) {
+                // Do cleanup to reduce memory footprint if no
+                // elements to be tracked
+                this.containerMap.remove(p);
+            } else {
+                this.containerMap.put(p, containers);
+            }
+        }
+    }
+
+    @Override
+    public void containerModeUpdated(UpdateType t) {
+        // do nothing
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---Topology Service Shim---\n");
+        help
+                .append("\t pem [container]               - Print edgeMap entries for a given container\n");
+        return help.toString();
+    }
+
+    public void _pem(CommandInterpreter ci) {
+        String container = ci.nextArgument();
+        if (container == null) {
+            container = GlobalConstants.DEFAULT.toString();
+        }
+
+        ci.println("Container: " + container);
+        ci
+                .println("                             Edge                                          Bandwidth");
+
+        Map<NodeConnector, Pair<Edge, Set<Property>>> edgePropsMap = edgeMap
+                .get(container);
+        if (edgePropsMap == null) {
+            return;
+        }
+        for (Pair<Edge, Set<Property>> edgeProps : edgePropsMap.values()) {
+            if (edgeProps == null) {
+                continue;
+            }
+
+            long bw = 0;
+            for (Property prop : edgeProps.getRight()) {
+                if (prop.getName().equals(Bandwidth.BandwidthPropName)) {
+                    bw = ((Bandwidth) prop).getValue();
+                }
+            }
+
+            ci.println(edgeProps.getLeft() + "          " + bw);
+        }
+    }
+
+    public void _bwfactor(CommandInterpreter ci) {
+        String factorString = ci.nextArgument();
+        if (factorString == null) {
+            ci.println("Bw threshold: " + this.bwThresholdFactor);
+            ci.println("Insert a non null bw threshold");
+            return;
+        }
+        bwThresholdFactor = Float.parseFloat(factorString);
+        ci.println("New Bw threshold: " + this.bwThresholdFactor);
+    }
+
+    /**
+     * This method will trigger topology updates to be sent
+     * toward SAL.  SAL then pushes the updates to ALL the applications
+     * that have registered as listeners for this service.  SAL has no
+     * way of knowing which application requested for the refresh.
+     *
+     * As an example of this case, is stopping and starting the
+     * Topology Manager.  When the topology Manager is stopped,
+     * and restarted, it will no longer have the latest topology.
+     * Hence, a request is sent here.
+     *
+     * @param containerName
+     * @return void
+     */
+    @Override
+    public void requestRefresh(String containerName) {
+        // wake up a bulk update thread and exit
+        // the thread will execute the bulkUpdate()
+        bulkNotifyQ.add(containerName);
+    }
+
+    /**
+     * Reading the current topology database, the method will replay
+     * all the edge updates for the ITopologyServiceShimListener instance
+     * in the given container, which will in turn publish them toward SAL.
+     * @param containerName
+     */
+    private void TopologyBulkUpdate(String containerName) {
+        Map<NodeConnector, Pair<Edge, Set<Property>>> edgePropMap = null;
+
+        logger.debug("Try bulk update for container:{}", containerName);
+        edgePropMap = edgeMap.get(containerName);
+        if (edgePropMap == null) {
+            logger.debug("No edges known for container:{}", containerName);
+            return;
+        }
+        ITopologyServiceShimListener topologServiceShimListener = topologyServiceShimListeners
+                .get(containerName);
+        if (topologServiceShimListener == null) {
+            logger.debug("No topology service shim listener for container:{}",
+                    containerName);
+            return;
+        }
+        int i = 0;
+        for (Pair<Edge, Set<Property>> edgeProps : edgePropMap.values()) {
+            if (edgeProps != null) {
+                i++;
+                logger.trace("Add edge {}", edgeProps.getLeft());
+                topologServiceShimListener.edgeUpdate(edgeProps.getLeft(),
+                        UpdateType.ADDED, edgeProps.getRight());
+            }
+        }
+        logger.debug("Sent {} updates", i);
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/TopologyServices.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/TopologyServices.java
new file mode 100644 (file)
index 0000000..57a7b03
--- /dev/null
@@ -0,0 +1,153 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.util.Dictionary;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.protocol_plugin.openflow.IRefreshInternalProvider;
+import org.opendaylight.controller.protocol_plugin.openflow.ITopologyServiceShimListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.topology.IPluginInTopologyService;
+import org.opendaylight.controller.sal.topology.IPluginOutTopologyService;
+
+public class TopologyServices implements ITopologyServiceShimListener,
+        IPluginInTopologyService {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(TopologyServices.class);
+    private IPluginOutTopologyService salTopoService = null;
+    private IRefreshInternalProvider topoRefreshService = null;
+    private String containerName;
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    @SuppressWarnings("unchecked")
+    void init(Component c) {
+        logger.debug("INIT called!");
+        Dictionary<Object, Object> props = c.getServiceProperties();
+        containerName = (props != null) ? (String) props.get("containerName")
+                : null;
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        logger.debug("DESTROY called!");
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+        logger.debug("START called!");
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+        logger.debug("STOP called!");
+    }
+
+    /**
+     * Retrieve SAL service IPluginOutTopologyService
+     *
+     * @param s Called by Dependency Manager as soon as the SAL
+     * service is available
+     */
+    public void setPluginOutTopologyService(IPluginOutTopologyService s) {
+        logger.debug("Setting IPluginOutTopologyService to:" + s);
+        this.salTopoService = s;
+    }
+
+    /**
+     * called when SAL service IPluginOutTopologyService is no longer available
+     *
+     * @param s Called by Dependency Manager as soon as the SAL
+     * service is unavailable
+     */
+    public void unsetPluginOutTopologyService(IPluginOutTopologyService s) {
+        if (this.salTopoService == s) {
+            logger.debug("UNSetting IPluginOutTopologyService from:" + s);
+            this.salTopoService = null;
+        }
+    }
+
+    /**
+     * Retrieve OF protocol_plugin service IRefreshInternalProvider
+     *
+     * @param s Called by Dependency Manager as soon as the SAL
+     * service is available
+     */
+    public void setRefreshInternalProvider(IRefreshInternalProvider s) {
+        logger.debug("Setting IRefreshInternalProvider to:" + s);
+        this.topoRefreshService = s;
+    }
+
+    /**
+     * called when OF protocol_plugin service IRefreshInternalProvider
+     * is no longer available
+     *
+     * @param s Called by Dependency Manager as soon as the SAL
+     * service is unavailable
+     */
+    public void unsetRefreshInternalProvider(IRefreshInternalProvider s) {
+        if (this.topoRefreshService == s) {
+            logger.debug("UNSetting IRefreshInternalProvider from:" + s);
+            this.topoRefreshService = null;
+        }
+    }
+
+    @Override
+    public void edgeUpdate(Edge edge, UpdateType type, Set<Property> props) {
+        if (this.salTopoService != null) {
+            this.salTopoService.edgeUpdate(edge, type, props);
+        }
+    }
+
+    @Override
+    public void sollicitRefresh() {
+        logger.debug("Got a request to refresh topology");
+        topoRefreshService.requestRefresh(containerName);
+    }
+
+    @Override
+    public void edgeOverUtilized(Edge edge) {
+        if (this.salTopoService != null) {
+            this.salTopoService.edgeOverUtilized(edge);
+        }
+    }
+
+    @Override
+    public void edgeUtilBackToNormal(Edge edge) {
+        if (this.salTopoService != null) {
+            this.salTopoService.edgeUtilBackToNormal(edge);
+        }
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/Utils.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/internal/Utils.java
new file mode 100644 (file)
index 0000000..77a39e9
--- /dev/null
@@ -0,0 +1,61 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFError.OFBadActionCode;
+import org.openflow.protocol.OFError.OFBadRequestCode;
+import org.openflow.protocol.OFError.OFErrorType;
+import org.openflow.protocol.OFError.OFFlowModFailedCode;
+import org.openflow.protocol.OFError.OFHelloFailedCode;
+import org.openflow.protocol.OFError.OFPortModFailedCode;
+import org.openflow.protocol.OFError.OFQueueOpFailedCode;
+
+public abstract class Utils {
+    public static String getOFErrorString(OFError error) {
+        OFErrorType et = OFErrorType.values()[0xffff & error.getErrorType()];
+        String errorStr = "Error : " + et.toString();
+        switch (et) {
+        case OFPET_HELLO_FAILED:
+            OFHelloFailedCode hfc = OFHelloFailedCode.values()[0xffff & error
+                    .getErrorCode()];
+            errorStr += " " + hfc.toString();
+            break;
+        case OFPET_BAD_REQUEST:
+            OFBadRequestCode brc = OFBadRequestCode.values()[0xffff & error
+                    .getErrorCode()];
+            errorStr += " " + brc.toString();
+            break;
+        case OFPET_BAD_ACTION:
+            OFBadActionCode bac = OFBadActionCode.values()[0xffff & error
+                    .getErrorCode()];
+            errorStr += " " + bac.toString();
+            break;
+        case OFPET_FLOW_MOD_FAILED:
+            OFFlowModFailedCode fmfc = OFFlowModFailedCode.values()[0xffff & error
+                    .getErrorCode()];
+            errorStr += " " + fmfc.toString();
+            break;
+        case OFPET_PORT_MOD_FAILED:
+            OFPortModFailedCode pmfc = OFPortModFailedCode.values()[0xffff & error
+                    .getErrorCode()];
+            errorStr += " " + pmfc.toString();
+            break;
+        case OFPET_QUEUE_OP_FAILED:
+            OFQueueOpFailedCode qofc = OFQueueOpFailedCode.values()[0xffff & error
+                    .getErrorCode()];
+            errorStr += " " + qofc.toString();
+            break;
+        default:
+            break;
+        }
+        return errorStr;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6FlowMod.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6FlowMod.java
new file mode 100644 (file)
index 0000000..47bb221
--- /dev/null
@@ -0,0 +1,225 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.action.OFAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+
+/**
+ * This class is used to create IPv6 Vendor Extension messages. Specfically, It
+ * defines the methods used in creation of Vendor specific IPv6 Flow Mod message.
+ * 
+ *
+ */
+public class V6FlowMod extends OFVendor implements Cloneable {
+    private static final Logger logger = LoggerFactory
+            .getLogger(V6FlowMod.class);
+    private static final long serialVersionUID = 1L;
+    protected V6Match match;
+    protected long cookie;
+    protected short command;
+    protected short idleTimeout;
+    protected short hardTimeout;
+    protected short priority;
+    protected int bufferId;
+    protected short outPort;
+    protected short flags;
+    protected List<OFAction> actions;
+    short match_len;
+    short actions_len;
+    short pad_size;
+
+    private static int IPV6EXT_ADD_FLOW_MSG_TYPE = 13;
+    private static int IPV6_EXT_MIN_HDR_LEN = 36;
+
+    /**
+     * Constructor for the V6FlowMod class. Initializes OFVendor (parent class) 
+     * fields by calling the parent class' constructor.
+     */
+    public V6FlowMod() {
+        super();
+    }
+
+    /**
+     * This method sets the match fields of V6FlowMod object
+     * @param match            V6Match object for this V6FlowMod message
+     */
+    public void setMatch(V6Match match) {
+        this.match = match;
+    }
+
+    /**
+     * Sets the list of actions V6FlowMod message
+     * @param actions  a list of ordered OFAction objects
+     */
+    public void setActions(List<OFAction> actions) {
+        this.actions = actions;
+    }
+
+    /**
+     * Sets the priority field of V6FlowMod message
+     * @param priority         Priority of the message
+     */
+    public void setPriority(short priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * Sets the cookie field of V6FlowMod message
+     * @param cookie   Cookie of the message
+     */
+    public void setCookie(long cookie) {
+        this.cookie = cookie;
+    }
+
+    /**
+     * Sets the command field of V6FlowMod message
+     * @param command  Command type of the message (ADD or DELETE)
+     */
+    public V6FlowMod setCommand(short command) {
+        this.command = command;
+        return this;
+    }
+
+    /**
+     * Sets the outPort field of V6FlowMod message
+     * @param outPort  outPort of the message
+     */
+    public V6FlowMod setOutPort(OFPort port) {
+        this.outPort = port.getValue();
+        return this;
+    }
+
+    /**
+     * Sets the idle_timeout of V6FlowMod message
+     * @param idleTimeout      idle timeout for this message
+     */
+    public void setIdleTimeout(short idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    /**
+     * Sets the hardTimeout field of V6FlowMod message
+     * @param hardTimeout      hard timeout of the message
+     */
+    public void setHardTimeout(short hardTimeout) {
+        this.hardTimeout = hardTimeout;
+    }
+
+    /**
+     * Returns the Flow Mod message subtype for V6FlowMod message
+     * @return                 message subtype
+     */
+    private int getIPv6ExtensionFlowModAddSubType() {
+        return IPV6EXT_ADD_FLOW_MSG_TYPE;
+    }
+    
+    /**
+     * Returns the minimum header size for V6Flow Message type
+     * @return         minimum header size
+     */
+
+    public int getV6FlowModMinHdrSize() {
+        return IPV6_EXT_MIN_HDR_LEN;
+    }
+    
+    /**
+     * Sets the Vendor type in OFVendor message
+     */
+
+    public void setVendor() {
+        super.setVendor(V6StatsRequest.NICIRA_VENDOR_ID);
+    }
+    
+    /**
+     * This method forms the Vendor extension IPv6 Flow Mod message.It uses the
+     * fields in V6FlowMod class, and writes the data according to vendor 
+     * extension format. The fields include flow properties (cookie, timeout,
+     * priority, etc), flow match, and action list. It also takes care of 
+     * required padding.
+     */
+
+    @Override
+    public void writeTo(ByteBuffer data) {
+        super.writeTo(data);
+        data.putInt(getIPv6ExtensionFlowModAddSubType());
+        data.putLong(this.cookie);
+        data.putShort(command); /* should be OFPFC_ADD, OFPFC_DELETE_STRICT, etc*/
+        data.putShort(this.idleTimeout);
+        data.putShort(this.hardTimeout);
+        data.putShort(this.priority);
+        data.putInt(OFPacketOut.BUFFER_ID_NONE);
+        data.putShort(outPort); /* output_port */
+        data.putShort((short) 0); /* flags */
+        match_len = this.match.getIPv6MatchLen();
+        data.putShort(match_len);
+        byte[] pad = new byte[6];
+        data.put(pad);
+        this.match.writeTo(data);
+
+        pad_size = (short) (((match_len + 7) / 8) * 8 - match_len);
+
+        /*
+         * action list should be preceded by a padding of 0 to 7 bytes based upon
+         * above formula.
+         */
+
+        byte[] pad2 = new byte[pad_size];
+        data.put(pad2);
+        if (actions != null) {
+            for (OFAction action : actions) {
+                actions_len += action.getLength();
+                action.writeTo(data);
+            }
+        }
+        logger.trace("{}", this.toString());
+    }
+
+    /**
+     * Forms the clone of V6FlowMod Object. If Object is returned
+     * successfully, then returns the cloned object. Throws an 
+     * exception if cloning is not supported.
+     */
+    @Override
+    public V6FlowMod clone() {
+        try {
+            V6Match neoMatch = match.clone();
+            V6FlowMod v6flowMod = (V6FlowMod) super.clone();
+            v6flowMod.setMatch(neoMatch);
+            List<OFAction> neoActions = new LinkedList<OFAction>();
+            for (OFAction action : this.actions)
+                neoActions.add((OFAction) action.clone());
+            v6flowMod.setActions(neoActions);
+            return v6flowMod;
+        } catch (CloneNotSupportedException e) {
+            // Won't happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Prints the contents of V6FlowMod in a string format.
+     */
+    @Override
+    public String toString() {
+        return "V6FlowMod[" + ReflectionToStringBuilder.toString(this) + "]";
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6Match.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6Match.java
new file mode 100644 (file)
index 0000000..294da72
--- /dev/null
@@ -0,0 +1,1351 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension;
+
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.openflow.protocol.OFMatch;
+import org.openflow.util.U16;
+import org.openflow.util.U8;
+
+import java.net.Inet6Address;
+import org.opendaylight.controller.sal.utils.HexEncode;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This Class forms the vendor specific IPv6 Flow Match messages as well as
+ * processes the vendor specific IPv6 Stats Reply message.
+ * 
+ * For message creation, it parses the user entered IPv6 match fields, creates 
+ * a sub-message for each field which are later used to form the complete 
+ * message.  
+ * 
+ * For message processing, it parses the incoming message and reads each field
+ * of the message and stores in appropriate field of V6Match object.
+ *  
+ *
+ */
+public class V6Match extends OFMatch implements Cloneable {
+    private static final Logger logger = LoggerFactory.getLogger(V6Match.class);
+    private static final long serialVersionUID = 1L;
+    protected InetAddress nwSrc;
+    protected InetAddress nwDst;
+    protected short inputPortMask;
+    protected byte[] dataLayerSourceMask;
+    protected byte[] dataLayerDestinationMask;
+    protected short dataLayerVirtualLanMask;
+    protected byte dataLayerVirtualLanPriorityCodePointMask;
+    protected short dataLayerTypeMask;
+    protected byte networkTypeOfServiceMask;
+    protected byte networkProtocolMask;
+    protected InetAddress networkSourceMask;
+    protected InetAddress networkDestinationMask;
+    protected short transportSourceMask;
+    protected short transportDestinationMask;
+    protected short srcIPv6SubnetMaskbits;
+    protected short dstIPv6SubnetMaskbits;
+
+    protected MatchFieldState inputPortState;
+    protected MatchFieldState dlSourceState;
+    protected MatchFieldState dlDestState;
+    protected MatchFieldState dlVlanState;
+    protected MatchFieldState ethTypeState;
+    protected MatchFieldState nwTosState;
+    protected MatchFieldState nwProtoState;
+    protected MatchFieldState nwSrcState;
+    protected MatchFieldState nwDstState;
+    protected MatchFieldState tpSrcState;
+    protected MatchFieldState tpDstState;
+    protected short match_len = 0;
+    protected short pad_size = 0;
+
+    private static int IPV6_EXT_MIN_HDR_LEN = 36;
+
+    private enum MatchFieldState {
+        MATCH_ABSENT, MATCH_FIELD_ONLY, MATCH_FIELD_WITH_MASK
+    }
+
+    private enum OF_Match_Types {
+        MATCH_OF_IN_PORT(0), MATCH_OF_ETH_DST(1), MATCH_OF_ETH_SRC(2), MATCH_OF_ETH_TYPE(
+                3), MATCH_OF_VLAN_TCI(4), MATCH_OF_IP_TOS(5), MATCH_OF_IP_PROTO(
+                6), MATCH_OF_IP_SRC(7), MATCH_OF_IP_DST(8), MATCH_OF_TCP_SRC(9), MATCH_OF_TCP_DST(
+                10), MATCH_OF_UDP_SRC(11), MATCH_OF_UDP_DST(12), MATCH_OF_ICMTP_TYPE(
+                13), MATCH_OF_ICMP_CODE(14), MATCH_OF_ARP_OP(15);
+
+        private int value;
+
+        private OF_Match_Types(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return this.value;
+        }
+    }
+
+    private enum IPv6Extension_Match_Types {
+        MATCH_IPV6EXT_TUN_ID(16), MATCH_IPV6EXT_ARP_SHA(17), MATCH_IPV6EXT_ARP_THA(
+                18), MATCH_IPV6EXT_IPV6_SRC(19), MATCH_IPV6EXT_IPV6_DST(20);
+
+        private int value;
+
+        private IPv6Extension_Match_Types(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
+    public enum Extension_Types {
+        OF_10(0), IPV6EXT(1);
+
+        protected int value;
+
+        private Extension_Types(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
+    public V6Match() {
+        super();
+
+        this.nwSrc = null;
+        this.nwDst = null;
+
+        this.inputPortMask = 0;
+        this.dataLayerSourceMask = null;
+        this.dataLayerDestinationMask = null;
+        this.dataLayerTypeMask = 0;
+        this.dataLayerVirtualLanMask = 0;
+        this.dataLayerVirtualLanPriorityCodePointMask = 0;
+        this.networkDestinationMask = null;
+        this.networkSourceMask = null;
+        this.networkTypeOfServiceMask = 0;
+        this.networkProtocolMask = 0;
+        this.transportSourceMask = 0;
+        this.transportDestinationMask = 0;
+
+        this.inputPortState = MatchFieldState.MATCH_ABSENT;
+        this.dlSourceState = MatchFieldState.MATCH_ABSENT;
+        this.dlDestState = MatchFieldState.MATCH_ABSENT;
+        this.dlVlanState = MatchFieldState.MATCH_ABSENT;
+        this.ethTypeState = MatchFieldState.MATCH_ABSENT;
+        this.nwTosState = MatchFieldState.MATCH_ABSENT;
+        this.nwProtoState = MatchFieldState.MATCH_ABSENT;
+        this.nwSrcState = MatchFieldState.MATCH_ABSENT;
+        this.nwDstState = MatchFieldState.MATCH_ABSENT;
+        this.tpSrcState = MatchFieldState.MATCH_ABSENT;
+        this.tpDstState = MatchFieldState.MATCH_ABSENT;
+
+        this.match_len = 0;
+        this.pad_size = 0;
+    }
+
+    public V6Match(OFMatch match) {
+        super();
+        this.match_len = 0;
+        this.pad_size = 0;
+
+        this.networkSourceMask = null;
+        if (match.getNetworkSource() != 0) {
+            InetAddress address = null;
+            try {
+                address = InetAddress.getByAddress(ByteBuffer.allocate(4)
+                        .putInt(match.getNetworkSource()).array());
+            } catch (UnknownHostException e) {
+                e.printStackTrace();
+            }
+            this.setNetworkSource(address, null);
+        } else {
+            this.nwSrc = null;
+            this.nwSrcState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.networkDestinationMask = null;
+        if (match.getNetworkDestination() != 0) {
+            InetAddress address = null;
+            try {
+                address = InetAddress.getByAddress(ByteBuffer.allocate(4)
+                        .putInt(match.getNetworkDestination()).array());
+            } catch (UnknownHostException e) {
+                e.printStackTrace();
+            }
+            this.setNetworkDestination(address, null);
+        } else {
+            this.nwDst = null;
+            this.nwDstState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.inputPortMask = 0;
+        if (match.getInputPort() != 0) {
+            this.setInputPort(match.getInputPort(), (short) 0);
+        } else {
+            this.inputPortMask = 0;
+            this.inputPortState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.dataLayerSourceMask = null;
+        if (match.getDataLayerSource() != null) {
+            this.setDataLayerSource(match.getDataLayerSource(), null);
+        } else {
+            this.dataLayerSource = null;
+            this.dlSourceState = MatchFieldState.MATCH_ABSENT;
+        }
+        this.dataLayerDestinationMask = null;
+        if (match.getDataLayerDestination() != null) {
+            this.setDataLayerDestination(match.getDataLayerDestination(), null);
+        } else {
+            this.dataLayerDestination = null;
+            this.dlDestState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.dataLayerTypeMask = 0;
+        if (match.getDataLayerType() != 0) {
+            this.setDataLayerType(match.getDataLayerType(), (short) 0);
+        } else {
+            this.dataLayerType = 0;
+            this.ethTypeState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.dataLayerVirtualLanMask = 0;
+        if (match.getDataLayerVirtualLan() != 0) {
+            this.setDataLayerVirtualLan(match.getDataLayerVirtualLan(),
+                    (short) 0);
+        } else {
+            this.dataLayerVirtualLan = 0;
+            this.dlVlanState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.dataLayerVirtualLanPriorityCodePointMask = 0;
+        if (match.getDataLayerVirtualLanPriorityCodePoint() != 0) {
+            this.setDataLayerVirtualLanPriorityCodePoint(match
+                    .getDataLayerVirtualLanPriorityCodePoint(), (byte) 0);
+        } else {
+            this.dataLayerVirtualLanPriorityCodePoint = 0;
+        }
+
+        this.networkProtocolMask = 0;
+        if (match.getNetworkProtocol() != 0) {
+            this.setNetworkProtocol(this.networkProtocol = match
+                    .getNetworkProtocol(), (byte) 0);
+        } else {
+            this.networkProtocol = 0;
+            this.nwProtoState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.networkTypeOfServiceMask = 0;
+        if (match.getNetworkTypeOfService() != 0) {
+            this.setNetworkTypeOfService(this.networkTypeOfService = match
+                    .getNetworkTypeOfService(), (byte) 0);
+        } else {
+            this.networkTypeOfService = match.getNetworkTypeOfService();
+            this.nwTosState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.transportSourceMask = 0;
+        if (match.getTransportSource() != 0) {
+            this.setTransportSource(match.getTransportSource(), (short) 0);
+        } else {
+            this.transportSource = 0;
+            this.tpSrcState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.transportDestinationMask = 0;
+        if (match.getTransportDestination() != 0) {
+            this.setTransportDestination(match.getTransportDestination(),
+                    (short) 0);
+        } else {
+            this.transportDestination = 0;
+            this.tpDstState = MatchFieldState.MATCH_ABSENT;
+        }
+
+        this.setWildcards(match.getWildcards());
+    }
+
+    private enum IPProtocols {
+        ICMP(1), TCP(6), UDP(17), ICMPV6(58);
+
+        private int protocol;
+
+        private IPProtocols(int value) {
+            this.protocol = value;
+        }
+
+        private byte getValue() {
+            return (byte) this.protocol;
+        }
+    }
+
+    public short getIPv6MatchLen() {
+        return match_len;
+    }
+
+    public int getIPv6ExtMinHdrLen() {
+        return IPV6_EXT_MIN_HDR_LEN;
+    }
+
+    public short getPadSize() {
+        return (short) (((match_len + 7) / 8) * 8 - match_len);
+    }
+
+    private int getIPv6ExtensionMatchHeader(Extension_Types extType, int field,
+            int has_mask, int length) {
+        return (((extType.getValue() & 0x0000ffff) << 16)
+                | ((field & 0x0000007f) << 9) | ((has_mask & 0x00000001) << 8) | (length & 0x000000ff));
+    }
+
+    private byte[] getIPv6ExtensionPortMatchMsg(short port) {
+        ByteBuffer ipv6ext_port_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_IN_PORT.getValue(), 0, 2);
+        ipv6ext_port_msg.putInt(nxm_header);
+        ipv6ext_port_msg.putShort(port);
+        return (ipv6ext_port_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionDestMacMatchMsg(byte[] destMac) {
+        ByteBuffer ipv6ext_destmac_msg = ByteBuffer.allocate(10);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_ETH_DST.getValue(), 0, 6);
+        ipv6ext_destmac_msg.putInt(nxm_header);
+        ipv6ext_destmac_msg.put(destMac);
+        return (ipv6ext_destmac_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionSrcMacMatchMsg(byte[] srcMac) {
+        ByteBuffer ipv6ext_srcmac_msg = ByteBuffer.allocate(10);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_ETH_SRC.getValue(), 0, 6);
+        ipv6ext_srcmac_msg.putInt(nxm_header);
+        ipv6ext_srcmac_msg.put(srcMac);
+        return (ipv6ext_srcmac_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionEtherTypeMatchMsg(short EtherType) {
+        ByteBuffer ipv6ext_etype_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_ETH_TYPE.getValue(), 0, 2);
+        ipv6ext_etype_msg.putInt(nxm_header);
+        ipv6ext_etype_msg.putShort(EtherType);
+        return (ipv6ext_etype_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionVlanIDMatchMsg(short VLAN) {
+        ByteBuffer ipv6ext_vlanid_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_VLAN_TCI.getValue(), 0, 2);
+        ipv6ext_vlanid_msg.putInt(nxm_header);
+        ipv6ext_vlanid_msg.putShort(VLAN);
+        return (ipv6ext_vlanid_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionSrcIPv6MatchMsg(byte[] srcIpv6) {
+        ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(20);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT,
+                IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_SRC.getValue(), 0,
+                16);
+        ipv6ext_ipv6_msg.putInt(nxm_header);
+        ipv6ext_ipv6_msg.put(srcIpv6);
+        return (ipv6ext_ipv6_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionSrcIPv6MatchwithMaskMsg(byte[] srcIpv6,
+            short masklen) {
+        ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(36);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT,
+                IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_SRC.getValue(), 1,
+                32);
+        ipv6ext_ipv6_msg.putInt(nxm_header);
+        ipv6ext_ipv6_msg.put(srcIpv6);
+        byte[] ipv6_mask = getIPv6NetworkMaskinBytes(masklen);
+        ipv6ext_ipv6_msg.put(ipv6_mask);
+        return (ipv6ext_ipv6_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionDstIPv6MatchMsg(byte[] dstIpv6) {
+        ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(20);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT,
+                IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_DST.getValue(), 0,
+                16);
+        ipv6ext_ipv6_msg.putInt(nxm_header);
+        ipv6ext_ipv6_msg.put(dstIpv6);
+        return (ipv6ext_ipv6_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionDstIPv6MatchwithMaskMsg(byte[] dstIpv6,
+            short masklen) {
+        ByteBuffer ipv6ext_ipv6_msg = ByteBuffer.allocate(36);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.IPV6EXT,
+                IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_DST.getValue(), 1,
+                32);
+        ipv6ext_ipv6_msg.putInt(nxm_header);
+        ipv6ext_ipv6_msg.put(dstIpv6);
+        byte[] ipv6_mask = getIPv6NetworkMaskinBytes(masklen);
+        ipv6ext_ipv6_msg.put(ipv6_mask);
+        return (ipv6ext_ipv6_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionProtocolMatchMsg(byte protocol) {
+        ByteBuffer ipv6ext_proto_msg = ByteBuffer.allocate(5);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_IP_PROTO.getValue(), 0, 1);
+        if (protocol == 0) {
+            return null;
+        }
+        ipv6ext_proto_msg.putInt(nxm_header);
+        if (protocol == IPProtocols.ICMP.getValue()) {
+            /*
+             * The front end  passes the same protocol type values for IPv4
+             * and IPv6 flows. For the Protocol types we allow in our GUI
+             * (ICMP, TCP, UDP), ICMP is the only one which is different for
+             * IPv6. It is 1 for v4 and 58 for v6 Therefore, we overwrite it
+             * here.
+             */
+            protocol = IPProtocols.ICMPV6.getValue();
+        }
+        ipv6ext_proto_msg.put(protocol);
+        return (ipv6ext_proto_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionTOSMatchMsg(byte tos) {
+        ByteBuffer ipv6ext_tos_msg = ByteBuffer.allocate(5);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_IP_TOS.getValue(), 0, 1);
+        ipv6ext_tos_msg.putInt(nxm_header);
+        ipv6ext_tos_msg.put(tos);
+        return (ipv6ext_tos_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionTCPSrcPortMatchMsg(short src_port) {
+        ByteBuffer ipv6ext_tcp_srcport_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_TCP_SRC.getValue(), 0, 2);
+        ipv6ext_tcp_srcport_msg.putInt(nxm_header);
+        ipv6ext_tcp_srcport_msg.putShort(src_port);
+        return (ipv6ext_tcp_srcport_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionTCPDstPortMatchMsg(short dst_port) {
+        ByteBuffer ipv6ext_tcp_dstport_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_TCP_DST.getValue(), 0, 2);
+        ipv6ext_tcp_dstport_msg.putInt(nxm_header);
+        ipv6ext_tcp_dstport_msg.putShort(dst_port);
+        return (ipv6ext_tcp_dstport_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionUDPSrcPortMatchMsg(short src_port) {
+        ByteBuffer ipv6ext_udp_srcport_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_UDP_SRC.getValue(), 0, 2);
+        ipv6ext_udp_srcport_msg.putInt(nxm_header);
+        ipv6ext_udp_srcport_msg.putShort(src_port);
+        return (ipv6ext_udp_srcport_msg.array());
+    }
+
+    private byte[] getIPv6ExtensionUDPDstPortMatchMsg(short dst_port) {
+        ByteBuffer ipv6ext_udp_dstport_msg = ByteBuffer.allocate(6);
+        int nxm_header = getIPv6ExtensionMatchHeader(Extension_Types.OF_10,
+                OF_Match_Types.MATCH_OF_UDP_DST.getValue(), 0, 2);
+        ipv6ext_udp_dstport_msg.putInt(nxm_header);
+        ipv6ext_udp_dstport_msg.putShort(dst_port);
+        return (ipv6ext_udp_dstport_msg.array());
+    }
+
+    /**
+     * Sets this (V6Match) object's member variables based on a comma-separated key=value pair similar to OFMatch's fromString.
+     * 
+     * @param match a key=value comma separated string.
+     */
+    @Override
+    public void fromString(String match) throws IllegalArgumentException {
+        if (match.equals("") || match.equalsIgnoreCase("any")
+                || match.equalsIgnoreCase("all") || match.equals("[]"))
+            match = "OFMatch[]";
+        String[] tokens = match.split("[\\[,\\]]");
+        String[] values;
+        int initArg = 0;
+        if (tokens[0].equals("OFMatch"))
+            initArg = 1;
+        this.wildcards = OFPFW_ALL;
+        int i;
+        for (i = initArg; i < tokens.length; i++) {
+            values = tokens[i].split("=");
+            if (values.length != 2)
+                throw new IllegalArgumentException("Token " + tokens[i]
+                        + " does not have form 'key=value' parsing " + match);
+            values[0] = values[0].toLowerCase(); // try to make this case insens
+            if (values[0].equals(STR_IN_PORT) || values[0].equals("input_port")) {
+                this.inputPort = U16.t(Integer.valueOf(values[1]));
+                inputPortState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 6;
+            } else if (values[0].equals(STR_DL_DST)
+                    || values[0].equals("eth_dst")) {
+                this.dataLayerDestination = HexEncode
+                        .bytesFromHexString(values[1]);
+                dlDestState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 10;
+            } else if (values[0].equals(STR_DL_SRC)
+                    || values[0].equals("eth_src")) {
+                this.dataLayerSource = HexEncode.bytesFromHexString(values[1]);
+                dlSourceState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 10;
+                this.wildcards &= ~OFPFW_DL_SRC;
+            } else if (values[0].equals(STR_DL_TYPE)
+                    || values[0].equals("eth_type")) {
+                if (values[1].startsWith("0x"))
+                    this.dataLayerType = U16.t(Integer.valueOf(values[1]
+                            .replaceFirst("0x", ""), 16));
+                else
+                    this.dataLayerType = U16.t(Integer.valueOf(values[1]));
+                ethTypeState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 6;
+            } else if (values[0].equals(STR_DL_VLAN)) {
+                this.dataLayerVirtualLan = U16.t(Integer.valueOf(values[1]));
+                dlVlanState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 6;
+            } else if (values[0].equals(STR_DL_VLAN_PCP)) {
+                this.dataLayerVirtualLanPriorityCodePoint = U8.t(Short
+                        .valueOf(values[1]));
+                this.wildcards &= ~OFPFW_DL_VLAN_PCP;
+            } else if (values[0].equals(STR_NW_DST)
+                    || values[0].equals("ip_dst")) {
+                try {
+                    if (values[1].contains("/")) {
+                        String ipv6addr_wmask[] = values[1].split("/");
+                        this.nwDst = InetAddress.getByName(ipv6addr_wmask[0]);
+                        this.dstIPv6SubnetMaskbits = Short
+                                .valueOf(ipv6addr_wmask[1]);
+                        nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                        match_len += 36;
+                    } else {
+                        this.nwDst = InetAddress.getByName(values[1]);
+                        nwDstState = MatchFieldState.MATCH_FIELD_ONLY;
+                        match_len += 20;
+                    }
+                } catch (UnknownHostException e) {
+                    e.printStackTrace();
+                }
+            } else if (values[0].equals(STR_NW_SRC)
+                    || values[0].equals("ip_src")) {
+                try {
+                    if (values[1].contains("/")) {
+                        String ipv6addr_wmask[] = values[1].split("/");
+                        this.nwSrc = InetAddress.getByName(ipv6addr_wmask[0]);
+                        this.srcIPv6SubnetMaskbits = Short
+                                .valueOf(ipv6addr_wmask[1]);
+                        nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                        match_len += 36;
+                    } else {
+                        this.nwSrc = InetAddress.getByName(values[1]);
+                        nwSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+                        match_len += 20;
+                    }
+                } catch (UnknownHostException e) {
+                    e.printStackTrace();
+                }
+            } else if (values[0].equals(STR_NW_PROTO)) {
+                this.networkProtocol = U8.t(Short.valueOf(values[1]));
+                if (!(this.networkProtocol == 0)) {
+                    /*
+                     * if user selects proto 0, don't use it
+                     */
+                    nwProtoState = MatchFieldState.MATCH_FIELD_ONLY;
+                    match_len += 5;
+                }
+            } else if (values[0].equals(STR_NW_TOS)) {
+                this.networkTypeOfService = U8.t(Short.valueOf(values[1]));
+                nwTosState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 5;
+            } else if (values[0].equals(STR_TP_DST)) {
+                this.transportDestination = U16.t(Integer.valueOf(values[1]));
+                tpDstState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 6;
+            } else if (values[0].equals(STR_TP_SRC)) {
+                this.transportSource = U16.t(Integer.valueOf(values[1]));
+                tpSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+                match_len += 6;
+            } else
+                throw new IllegalArgumentException("unknown token " + tokens[i]
+                        + " parsing " + match);
+        }
+
+        /*
+         * In a V6 extension message action list should be preceded by a padding of 0 to
+         * 7 bytes based upon following formula.
+         */
+
+        pad_size = (short) (((match_len + 7) / 8) * 8 - match_len);
+
+    }
+
+    /**
+     * Write this message's binary format to the specified ByteBuffer
+     *
+     * @param data
+     */
+    @Override
+    public void writeTo(ByteBuffer data) {
+        if (inputPortState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_ingress_port_msg = getIPv6ExtensionPortMatchMsg(this.inputPort);
+            data.put(ipv6ext_ingress_port_msg);
+        }
+        if (ethTypeState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_ether_type_msg = getIPv6ExtensionEtherTypeMatchMsg(this.dataLayerType);
+            data.put(ipv6ext_ether_type_msg);
+        }
+        if (dlDestState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_destmac_msg = getIPv6ExtensionDestMacMatchMsg(this.dataLayerDestination);
+            data.put(ipv6ext_destmac_msg);
+        }
+        if (dlSourceState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_srcmac_msg = getIPv6ExtensionSrcMacMatchMsg(this.dataLayerSource);
+            data.put(ipv6ext_srcmac_msg);
+        }
+        if (dlVlanState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_vlan_id_msg = getIPv6ExtensionVlanIDMatchMsg(this.dataLayerVirtualLan);
+            data.put(ipv6ext_vlan_id_msg);
+        }
+        if (nwSrcState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_src_ipv6_msg = getIPv6ExtensionSrcIPv6MatchMsg(this.nwSrc
+                    .getAddress());
+            data.put(ipv6ext_src_ipv6_msg);
+        } else if (nwSrcState == MatchFieldState.MATCH_FIELD_WITH_MASK) {
+            byte[] ipv6ext_src_ipv6_with_mask_msg = getIPv6ExtensionSrcIPv6MatchwithMaskMsg(
+                    this.nwSrc.getAddress(), this.srcIPv6SubnetMaskbits);
+            data.put(ipv6ext_src_ipv6_with_mask_msg);
+        }
+        if (nwDstState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_dst_ipv6_msg = getIPv6ExtensionDstIPv6MatchMsg(this.nwDst
+                    .getAddress());
+            data.put(ipv6ext_dst_ipv6_msg);
+        } else if (nwDstState == MatchFieldState.MATCH_FIELD_WITH_MASK) {
+            byte[] ipv6ext_dst_ipv6_with_mask_msg = getIPv6ExtensionDstIPv6MatchwithMaskMsg(
+                    this.nwDst.getAddress(), this.dstIPv6SubnetMaskbits);
+            data.put(ipv6ext_dst_ipv6_with_mask_msg);
+        }
+        if (nwProtoState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_protocol_msg = getIPv6ExtensionProtocolMatchMsg(this.networkProtocol);
+            if (ipv6ext_protocol_msg != null) {
+                data.put(ipv6ext_protocol_msg);
+            }
+        }
+        if (nwTosState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_tos_msg = getIPv6ExtensionTOSMatchMsg(this.networkTypeOfService);
+            data.put(ipv6ext_tos_msg);
+        }
+        if (tpSrcState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_srcport_msg = null;
+            if (this.networkProtocol == IPProtocols.TCP.getValue()) {
+                ipv6ext_srcport_msg = getIPv6ExtensionTCPSrcPortMatchMsg(this.transportSource);
+            } else if (this.networkProtocol == IPProtocols.UDP.getValue()) {
+                ipv6ext_srcport_msg = getIPv6ExtensionUDPSrcPortMatchMsg(this.transportSource);
+            }
+            if (ipv6ext_srcport_msg != null) {
+                data.put(ipv6ext_srcport_msg);
+            }
+        }
+        if (tpDstState == MatchFieldState.MATCH_FIELD_ONLY) {
+            byte[] ipv6ext_dstport_msg = null;
+            if (this.networkProtocol == IPProtocols.TCP.getValue()) {
+                ipv6ext_dstport_msg = getIPv6ExtensionTCPDstPortMatchMsg(this.transportDestination);
+            } else if (this.networkProtocol == IPProtocols.UDP.getValue()) {
+                ipv6ext_dstport_msg = getIPv6ExtensionUDPDstPortMatchMsg(this.transportDestination);
+            }
+            if (ipv6ext_dstport_msg != null) {
+                data.put(ipv6ext_dstport_msg);
+            }
+        }
+        logger.trace("{}", this.toString());
+    }
+
+    private void readInPort(ByteBuffer data, int nxmLen, boolean hasMask) {
+        if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask))
+            /*
+             * mask is not allowed for inport port
+             */
+            return;
+        super.setInputPort(data.getShort());
+        this.inputPortState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.wildcards ^= (1 << 0); // Sync with 0F 1.0 Match
+        this.match_len += 6;
+    }
+
+    private void readDataLinkDestination(ByteBuffer data, int nxmLen,
+            boolean hasMask) {
+        if (hasMask) {
+            if ((nxmLen != 2 * 6) || (data.remaining() < 2 * 6))
+                return;
+            else {
+                byte[] bytes = new byte[6];
+                data.get(bytes);
+                super.setDataLayerDestination(bytes);
+                this.dataLayerDestinationMask = new byte[6];
+                data.get(this.dataLayerDestinationMask);
+                this.dlDestState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                this.match_len += 16;
+            }
+        } else {
+            if ((nxmLen != 6) || (data.remaining() < 6))
+                return;
+            else {
+                byte[] bytes = new byte[6];
+                data.get(bytes);
+                super.setDataLayerDestination(bytes);
+                this.dlDestState = MatchFieldState.MATCH_FIELD_ONLY;
+                this.match_len += 10;
+            }
+        }
+        this.wildcards ^= (1 << 3); // Sync with 0F 1.0 Match
+    }
+
+    private void readDataLinkSource(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in data link source
+         */
+        if ((nxmLen != 6) || (data.remaining() < 6) || (hasMask))
+            return;
+        byte[] bytes = new byte[6];
+        data.get(bytes);
+        super.setDataLayerSource(bytes);
+        this.dlSourceState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 10;
+        this.wildcards ^= (1 << 2); // Sync with 0F 1.0 Match
+    }
+
+    private void readEtherType(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in ethertype
+         */
+        if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask))
+            return;
+        super.setDataLayerType(data.getShort());
+        this.ethTypeState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.wildcards ^= (1 << 4); // Sync with 0F 1.0 Match
+        this.match_len += 6;
+    }
+
+    private void readVlanTci(ByteBuffer data, int nxmLen, boolean hasMask) {
+        short vlan_mask = 0xfff;
+        if (hasMask) {
+            if ((nxmLen != 2 * 2) || (data.remaining() < 2 * 2))
+                return;
+            else {
+                short vlan = data.getShort();
+                vlan &= vlan_mask;
+                super.setDataLayerVirtualLan(vlan);
+                this.dataLayerVirtualLanMask = data.getShort();
+                this.dlVlanState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                this.match_len += 8;
+                this.wildcards ^= (1 << 20);
+            }
+        } else {
+            if ((nxmLen != 2) || (data.remaining() < 2))
+                return;
+            else {
+                short vlan = data.getShort();
+                vlan &= vlan_mask;
+                super.setDataLayerVirtualLan(vlan);
+                this.dlVlanState = MatchFieldState.MATCH_FIELD_ONLY;
+                this.match_len += 6;
+            }
+        }
+
+        this.wildcards ^= (1 << 1); // Sync with 0F 1.0 Match
+    }
+
+    private void readIpTos(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in IP TOS
+         */
+        if ((nxmLen != 1) || (data.remaining() < 1) || (hasMask))
+            return;
+        super.setNetworkTypeOfService(data.get());
+        this.nwTosState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 5;
+        this.wildcards ^= (1 << 21); // Sync with 0F 1.0 Match
+    }
+
+    private void readIpProto(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in IP protocol
+         */
+        if ((nxmLen != 1) || (data.remaining() < 1) || (hasMask))
+            return;
+        super.setNetworkProtocol(data.get());
+        this.nwProtoState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 5;
+        this.wildcards ^= (1 << 5); // Sync with 0F 1.0 Match
+    }
+
+    private void readIpv4Src(ByteBuffer data, int nxmLen, boolean hasMask) {
+        if (hasMask) {
+            if ((nxmLen != 2 * 4) || (data.remaining() < 2 * 4))
+                return;
+            else {
+                byte[] sbytes = new byte[4];
+                data.get(sbytes);
+                try {
+                    this.nwSrc = InetAddress.getByAddress(sbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                byte[] mbytes = new byte[4];
+                data.get(mbytes);
+                try {
+                    this.networkSourceMask = InetAddress.getByAddress(mbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                this.match_len += 12;
+                int prefixlen = getNetworkMaskPrefixLength(mbytes);
+                this.wildcards ^= (((1 << 6) - 1) << 8); // Sync with 0F 1.0 Match
+                this.wildcards |= ((32 - prefixlen) << 8); // Sync with 0F 1.0 Match
+
+            }
+        } else {
+            if ((nxmLen != 4) || (data.remaining() < 4))
+                return;
+            else {
+                byte[] sbytes = new byte[4];
+                data.get(sbytes);
+                try {
+                    this.nwSrc = InetAddress.getByAddress(sbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+                this.match_len += 8;
+                this.wildcards ^= (((1 << 6) - 1) << 8); // Sync with 0F 1.0 Match
+            }
+        }
+    }
+
+    private void readIpv4Dst(ByteBuffer data, int nxmLen, boolean hasMask) {
+        if (hasMask) {
+            if ((nxmLen != 2 * 4) || (data.remaining() < 2 * 4))
+                return;
+            else {
+                byte[] dbytes = new byte[4];
+                data.get(dbytes);
+                try {
+                    this.nwDst = InetAddress.getByAddress(dbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                byte[] mbytes = new byte[4];
+                data.get(mbytes);
+                try {
+                    this.networkDestinationMask = InetAddress
+                            .getByAddress(mbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                this.match_len += 12;
+                int prefixlen = getNetworkMaskPrefixLength(mbytes);
+                this.wildcards ^= (((1 << 6) - 1) << 14); // Sync with 0F 1.0 Match
+                this.wildcards |= ((32 - prefixlen) << 14); // Sync with 0F 1.0 Match
+            }
+        } else {
+            if ((nxmLen != 4) || (data.remaining() < 4))
+                return;
+            else {
+                byte[] dbytes = new byte[4];
+                data.get(dbytes);
+                try {
+                    this.nwDst = InetAddress.getByAddress(dbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY;
+                this.wildcards ^= (((1 << 6) - 1) << 14); // Sync with 0F 1.0 Match
+                this.match_len += 8;
+            }
+        }
+    }
+
+    private void readTcpSrc(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in TCP SRC
+         */
+        if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask))
+            return;
+        super.setTransportSource(data.getShort());
+        this.tpSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+        this.wildcards ^= (1 << 6); // Sync with 0F 1.0 Match
+    }
+
+    private void readTcpDst(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in TCP DST
+         */
+        if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask))
+            return;
+        super.setTransportDestination(data.getShort());
+        this.tpDstState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+        this.wildcards ^= (1 << 7); // Sync with 0F 1.0 Match
+    }
+
+    private void readUdpSrc(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in UDP SRC
+         */
+        if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask))
+            return;
+        super.setTransportSource(data.getShort());
+        this.tpSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+        this.wildcards ^= (1 << 6); // Sync with 0F 1.0 Match
+    }
+
+    private void readUdpDst(ByteBuffer data, int nxmLen, boolean hasMask) {
+        /*
+         * mask is not allowed in UDP DST
+         */
+        if ((nxmLen != 2) || (data.remaining() < 2) || (hasMask))
+            return;
+        super.setTransportDestination(data.getShort());
+        this.tpDstState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+        this.wildcards ^= (1 << 7); // Sync with 0F 1.0 Match
+    }
+
+    private void readIpv6Src(ByteBuffer data, int nxmLen, boolean hasMask) {
+        if (hasMask) {
+            if ((nxmLen != 2 * 16) || (data.remaining() < 2 * 16))
+                return;
+            else {
+                byte[] sbytes = new byte[16];
+                data.get(sbytes);
+                try {
+                    this.nwSrc = InetAddress.getByAddress(sbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                byte[] mbytes = new byte[16];
+                data.get(mbytes);
+                try {
+                    this.networkSourceMask = InetAddress.getByAddress(mbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                this.match_len += 36;
+            }
+        } else {
+            if ((nxmLen != 16) || (data.remaining() < 16))
+                return;
+            else {
+                byte[] sbytes = new byte[16];
+                data.get(sbytes);
+                try {
+                    this.nwSrc = InetAddress.getByAddress(sbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+                this.match_len += 20;
+            }
+        }
+    }
+
+    private void readIpv6Dst(ByteBuffer data, int nxmLen, boolean hasMask) {
+        if (hasMask) {
+            if ((nxmLen != 2 * 16) || (data.remaining() < 2 * 16))
+                return;
+            else {
+                byte[] dbytes = new byte[16];
+                data.get(dbytes);
+                try {
+                    this.nwDst = InetAddress.getByAddress(dbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                byte[] mbytes = new byte[16];
+                data.get(mbytes);
+                try {
+                    this.networkDestinationMask = InetAddress
+                            .getByAddress(mbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+                this.match_len += 36;
+            }
+        } else {
+            if ((nxmLen != 16) || (data.remaining() < 16))
+                return;
+            else {
+                byte[] dbytes = new byte[16];
+                data.get(dbytes);
+                try {
+                    this.nwDst = InetAddress.getByAddress(dbytes);
+                } catch (UnknownHostException e) {
+                    return;
+                }
+                this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY;
+                this.match_len += 20;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "V6Match[" + ReflectionToStringBuilder.toString(this) + "]";
+    }
+
+    /**
+     * Read the data corresponding to the match field (received from the wire)
+     * Input: data: match field(s). Since match field is of variable length, the whole data that are passed in
+     * are assumed to fem0tbd.be the match fields.
+     * @param data
+     */
+    @Override
+    public void readFrom(ByteBuffer data) {
+        readFromInternal(data);
+        postprocessWildCardInfo();
+    }
+
+    private void readFromInternal(ByteBuffer data) {
+        this.match_len = 0;
+        while (data.remaining() > 0) {
+            if (data.remaining() < 4) {
+                /*
+                 * at least 4 bytes for each match header
+                 */
+                logger.error("Invalid Vendor Extension Header. Size {}", data
+                        .remaining());
+                return;
+            }
+            /*
+             * read the 4 byte match header
+             */
+            int nxmVendor = data.getShort();
+            int b = data.get();
+            int nxmField = b >> 1;
+            boolean hasMask = ((b & 0x01) == 1) ? true : false;
+            int nxmLen = data.get();
+            if (nxmVendor == Extension_Types.OF_10.getValue()) {
+                if (nxmField == OF_Match_Types.MATCH_OF_IN_PORT.getValue()) {
+                    readInPort(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_ETH_DST
+                        .getValue()) {
+                    readDataLinkDestination(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_ETH_SRC
+                        .getValue()) {
+                    readDataLinkSource(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_ETH_TYPE
+                        .getValue()) {
+                    readEtherType(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_VLAN_TCI
+                        .getValue()) {
+                    readVlanTci(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_IP_TOS
+                        .getValue()) {
+                    readIpTos(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_IP_PROTO
+                        .getValue()) {
+                    readIpProto(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_IP_SRC
+                        .getValue()) {
+                    readIpv4Src(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_IP_DST
+                        .getValue()) {
+                    readIpv4Dst(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_TCP_SRC
+                        .getValue()) {
+                    readTcpSrc(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_TCP_DST
+                        .getValue()) {
+                    readTcpDst(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_UDP_SRC
+                        .getValue()) {
+                    readUdpSrc(data, nxmLen, hasMask);
+                } else if (nxmField == OF_Match_Types.MATCH_OF_UDP_DST
+                        .getValue()) {
+                    readUdpDst(data, nxmLen, hasMask);
+                } else {
+                    // unexpected nxmField
+                    return;
+                }
+            } else if (nxmVendor == Extension_Types.IPV6EXT.getValue()) {
+                if (nxmField == IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_SRC
+                        .getValue()) {
+                    readIpv6Src(data, nxmLen, hasMask);
+                } else if (nxmField == IPv6Extension_Match_Types.MATCH_IPV6EXT_IPV6_DST
+                        .getValue()) {
+                    readIpv6Dst(data, nxmLen, hasMask);
+                } else {
+                    // unexpected nxmField
+                    return;
+                }
+            } else {
+                // invalid nxmVendor
+                return;
+            }
+        }
+    }
+
+    private void postprocessWildCardInfo() {
+        // Sync with 0F 1.0 Match
+        if (super.getDataLayerType() == 0x800) {
+            if (((this.wildcards >> 8) & 0x3f) == 0x3f) {
+                //ipv4 src processing
+                this.wildcards ^= (((1 << 5) - 1) << 8);
+            }
+            if (((this.wildcards >> 14) & 0x3f) == 0x3f) {
+                //ipv4 dest processing
+                this.wildcards ^= (((1 << 5) - 1) << 14);
+            }
+        } else {
+            this.wildcards = 0;
+        }
+    }
+
+    @Override
+    public V6Match clone() {
+
+        V6Match ret = (V6Match) super.clone();
+        try {
+            if (this.nwSrc != null) {
+                ret.nwSrc = InetAddress.getByAddress(this.nwSrc.getAddress());
+            }
+            if (this.nwDst != null) {
+                ret.nwDst = InetAddress.getByAddress(this.nwDst.getAddress());
+            }
+            return ret;
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Get nw_dst
+     *
+     * @return
+     */
+
+    public InetAddress getNetworkDest() {
+        return this.nwDst;
+    }
+
+    /**
+     * Get nw_src
+     *
+     * @return
+     */
+
+    public void setNetworkSrc(InetAddress address) {
+        nwSrc = address;
+    }
+
+    /**
+     * Set nw_dst
+     *
+     * @return
+     */
+
+    public void setNetworkDest(InetAddress address) {
+        nwDst = address;
+    }
+
+    /**
+     * Set nw_src
+     *
+     * @return
+     */
+
+    public InetAddress getNetworkSrc() {
+        return this.nwSrc;
+    }
+
+    private int getNetworkMaskPrefixLength(byte[] netMask) {
+        ByteBuffer nm = ByteBuffer.wrap(netMask);
+        int trailingZeros = Integer.numberOfTrailingZeros(nm.getInt());
+        return 32 - trailingZeros;
+    }
+
+    public short getInputPortMask() {
+        return inputPortMask;
+    }
+
+    public void setInputPort(short port, short mask) {
+        super.inputPort = port;
+        this.inputPortState = MatchFieldState.MATCH_FIELD_ONLY;
+        match_len += 6;
+        // Looks like mask is not allowed for input port. Will discard it
+    }
+
+    public byte[] getDataLayerSourceMask() {
+        return dataLayerSourceMask;
+    }
+
+    public void setDataLayerSource(byte[] mac, byte[] mask) {
+        if (mac != null) {
+            System.arraycopy(mac, 0, super.dataLayerSource, 0, mac.length);
+        }
+        if (mask == null) {
+            this.dlSourceState = MatchFieldState.MATCH_FIELD_ONLY;
+            this.match_len += 10;
+        } else {
+            if (this.dataLayerSourceMask == null) {
+                this.dataLayerSourceMask = new byte[mask.length];
+            }
+            System.arraycopy(mask, 0, this.dataLayerSourceMask, 0, mask.length);
+            this.dlSourceState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+            this.match_len += 16;
+        }
+    }
+
+    public byte[] getDataLayerDestinationMask() {
+        return dataLayerDestinationMask;
+    }
+
+    public void setDataLayerDestination(byte[] mac, byte[] mask) {
+        if (mac != null) {
+            System.arraycopy(mac, 0, super.dataLayerDestination, 0, mac.length);
+        }
+        if (mask == null) {
+            this.dlDestState = MatchFieldState.MATCH_FIELD_ONLY;
+            this.match_len += 10;
+        } else {
+            if (this.dataLayerDestinationMask == null) {
+                this.dataLayerDestinationMask = new byte[mask.length];
+            }
+            System.arraycopy(mask, 0, this.dataLayerDestinationMask, 0,
+                    mask.length);
+            this.dlDestState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+            this.match_len += 16;
+        }
+    }
+
+    public short getDataLayerVirtualLanMask() {
+        return dataLayerVirtualLanMask;
+    }
+
+    public void setDataLayerVirtualLan(short vlan, short mask) {
+        super.dataLayerVirtualLan = vlan;
+        if (mask == 0) {
+            this.dlVlanState = MatchFieldState.MATCH_FIELD_ONLY;
+            this.match_len += 6;
+        } else {
+            this.dataLayerVirtualLanMask = mask;
+            this.dlVlanState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+            this.match_len += 8;
+        }
+    }
+
+    public void setDataLayerVirtualLanPriorityCodePoint(byte pcp, byte mask) {
+        super.dataLayerVirtualLanPriorityCodePoint = pcp;
+    }
+
+    public void setDataLayerType(short ethType, short mask) {
+        // mask not allowed
+        super.dataLayerType = ethType;
+        this.ethTypeState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+    }
+
+    public void setNetworkTypeOfService(byte tos, byte mask) {
+        // mask not allowed
+        super.networkTypeOfService = tos;
+        this.nwTosState = MatchFieldState.MATCH_FIELD_ONLY;
+        match_len += 5;
+    }
+
+    public void setNetworkProtocol(byte ipProto, byte mask) {
+        // mask not allowed
+        super.networkProtocol = ipProto;
+        this.nwProtoState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 5;
+    }
+
+    public InetAddress getNetworkSourceMask() {
+        return networkSourceMask;
+    }
+
+    public void setNetworkSource(InetAddress address, InetAddress mask) {
+        this.nwSrc = address;
+        if (mask == null) {
+            this.nwSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+            this.match_len += (address instanceof Inet6Address) ? 20 : 8;
+        } else {
+            this.networkSourceMask = mask;
+            this.nwSrcState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+            this.match_len += (address instanceof Inet6Address) ? 36 : 12;
+        }
+    }
+
+    public InetAddress getNetworkDestinationMask() {
+        return networkDestinationMask;
+    }
+
+    public void setNetworkDestination(InetAddress address, InetAddress mask) {
+        this.nwDst = address;
+        if (mask == null) {
+            this.nwDstState = MatchFieldState.MATCH_FIELD_ONLY;
+            this.match_len += (address instanceof Inet6Address) ? 20 : 8;
+        } else {
+            this.networkDestinationMask = mask;
+            this.nwDstState = MatchFieldState.MATCH_FIELD_WITH_MASK;
+            this.match_len += (address instanceof Inet6Address) ? 36 : 12;
+        }
+    }
+
+    public void setTransportSource(short tpSrc, short mask) {
+        // mask not allowed
+        super.transportSource = tpSrc;
+        this.tpSrcState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+    }
+
+    public short getTransportDestinationMask() {
+        return transportDestinationMask;
+    }
+
+    public void setTransportDestination(short tpDst, short mask) {
+        // mask not allowed
+        super.transportDestination = tpDst;
+        this.tpDstState = MatchFieldState.MATCH_FIELD_ONLY;
+        this.match_len += 6;
+    }
+
+    private byte[] getIPv6NetworkMaskinBytes(short num) {
+        byte[] nbytes = new byte[16];
+        int quot = num / 8;
+        int bits = num % 8;
+        int i;
+
+        for (i = 0; i < quot; i++) {
+            nbytes[i] = (byte) 0xff;
+        }
+
+        if (bits > 0) {
+            nbytes[i] = (byte) 0xff;
+            nbytes[i] <<= 8 - bits;
+        }
+        return nbytes;
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6StatsReply.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6StatsReply.java
new file mode 100644 (file)
index 0000000..1b14ab0
--- /dev/null
@@ -0,0 +1,330 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.statistics.OFVendorStatistics;
+import org.openflow.util.U16;
+
+/**
+ * This Class processes the OpenFlow Vendor Extension Reply message of a Stats
+ * Request. It parses the reply message and initializes fields of  V6StatsReply
+ * object. Multiple instances of this class objects are created and used by 
+ * ONE Controller's Troubleshooting Application.
+ * 
+ */
+
+public class V6StatsReply extends OFVendorStatistics {
+    private static final long serialVersionUID = 1L;
+
+    public static int MINIMUM_LENGTH = 48; //48 for nx_flow_stats
+
+    protected short length = (short) MINIMUM_LENGTH;
+    protected byte tableId;
+    protected int durationSeconds;
+    protected int durationNanoseconds;
+    protected short priority;
+    protected short idleTimeout;
+    protected short hardTimeout;
+    protected short match_len;
+    protected short idleAge;
+    protected short hardAge;
+    protected long cookie;
+    protected long packetCount;
+    protected long byteCount;
+    protected V6Match match;
+    protected List<OFAction> actions;
+
+    /**
+     * @return vendor id
+     */
+    public int getVendorId() {
+        return vendor;
+    }
+
+    /**
+     * @param vendor the vendor to set
+     */
+    public void setVendorId(int vendor) {
+        this.vendor = vendor;
+    }
+
+    /**
+     * @return the tableId
+     */
+    public byte getTableId() {
+        return tableId;
+    }
+
+    /**
+     * @param tableId the tableId to set
+     */
+    public void setTableId(byte tableId) {
+        this.tableId = tableId;
+    }
+
+    /**
+     * @return the durationSeconds
+     */
+    public int getDurationSeconds() {
+        return durationSeconds;
+    }
+
+    /**
+     * @param durationSeconds the durationSeconds to set
+     */
+    public void setDurationSeconds(int durationSeconds) {
+        this.durationSeconds = durationSeconds;
+    }
+
+    /**
+     * @return the durationNanoseconds
+     */
+    public int getDurationNanoseconds() {
+        return durationNanoseconds;
+    }
+
+    /**
+     * @param durationNanoseconds the durationNanoseconds to set
+     */
+    public void setDurationNanoseconds(int durationNanoseconds) {
+        this.durationNanoseconds = durationNanoseconds;
+    }
+
+    /**
+     * @return the priority
+     */
+    public short getPriority() {
+        return priority;
+    }
+
+    /**
+     * @param priority the priority to set
+     */
+    public void setPriority(short priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * @return the idleTimeout
+     */
+    public short getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    /**
+     * @param idleTimeout the idleTimeout to set
+     */
+    public void setIdleTimeout(short idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    /**
+     * @return the hardTimeout
+     */
+    public short getHardTimeout() {
+        return hardTimeout;
+    }
+
+    /**
+     * @param hardTimeout the hardTimeout to set
+     */
+    public void setHardTimeout(short hardTimeout) {
+        this.hardTimeout = hardTimeout;
+    }
+
+    /**
+     * @param match_len the match_len to set
+     */
+    public void setMatchLen(short match_len) {
+        this.match_len = match_len;
+    }
+
+    /**
+     * @return the match_len
+     */
+    public short getMatchLen() {
+        return match_len;
+    }
+
+    /**
+     * @return the idleAge
+     */
+    public short getIdleAge() {
+        return idleAge;
+    }
+
+    /**
+     * @param idleAge the idleAge to set
+     */
+    public void setIdleAge(short idleAge) {
+        this.idleAge = idleAge;
+    }
+
+    /**
+     * @return the hardAge
+     */
+    public short getHardAge() {
+        return hardAge;
+    }
+
+    /**
+     * @param hardAge the hardAge to set
+     */
+    public void setHardAge(short hardAge) {
+        this.hardAge = hardAge;
+    }
+
+    /**
+     * @return the cookie
+     */
+    public long getCookie() {
+        return cookie;
+    }
+
+    /**
+     * @param cookie the cookie to set
+     */
+    public void setCookie(long cookie) {
+        this.cookie = cookie;
+    }
+
+    /**
+     * @return the packetCount
+     */
+    public long getPacketCount() {
+        return packetCount;
+    }
+
+    /**
+     * @param packetCount the packetCount to set
+     */
+    public void setPacketCount(long packetCount) {
+        this.packetCount = packetCount;
+    }
+
+    /**
+     * @return the byteCount
+     */
+    public long getByteCount() {
+        return byteCount;
+    }
+
+    /**
+     * @param byteCount the byteCount to set
+     */
+    public void setByteCount(long byteCount) {
+        this.byteCount = byteCount;
+    }
+
+    /**
+     * @param length the length to set
+     */
+    public void setLength(short length) {
+        this.length = length;
+    }
+
+    @Override
+    public int getLength() {
+        return U16.f(length);
+    }
+
+    /**
+     * @return the match
+     */
+    public V6Match getMatch() {
+        return match;
+    }
+
+    /**
+     * @return the actions
+     */
+    public List<OFAction> getActions() {
+        return actions;
+    }
+
+    /**
+     * @param actions the actions to set
+     */
+    public void setActions(List<OFAction> actions) {
+        this.actions = actions;
+    }
+
+    @Override
+    public void readFrom(ByteBuffer data) {
+        short i;
+        this.length = data.getShort();
+        if (length < MINIMUM_LENGTH)
+            return; //TBD - Spurious Packet?
+        this.tableId = data.get();
+        data.get(); // pad
+        this.durationSeconds = data.getInt();
+        this.durationNanoseconds = data.getInt();
+        this.priority = data.getShort();
+        this.idleTimeout = data.getShort();
+        this.hardTimeout = data.getShort();
+        this.match_len = data.getShort();
+        this.idleAge = data.getShort();
+        this.hardAge = data.getShort();
+        this.cookie = data.getLong();
+        this.packetCount = data.getLong();
+        this.byteCount = data.getLong();
+        if (this.length == MINIMUM_LENGTH) {
+            return; //TBD - can this happen??
+        }
+        if (this.match == null)
+            this.match = new V6Match();
+        ByteBuffer mbuf = ByteBuffer.allocate(match_len);
+        for (i = 0; i < match_len; i++) {
+            mbuf.put(data.get());
+        }
+        mbuf.rewind();
+        this.match.readFrom(mbuf);
+        if (this.actionFactory == null)
+            throw new RuntimeException("OFActionFactory not set");
+        /*
+         * action list may be preceded by a padding of 0 to 7 bytes based upon this:
+         */
+        short pad_size = (short) (((match_len + 7) / 8) * 8 - match_len);
+        for (i = 0; i < pad_size; i++)
+            data.get();
+        int action_len = this.length - MINIMUM_LENGTH - (match_len + pad_size);
+        if (action_len > 0)
+            this.actions = this.actionFactory.parseActions(data, action_len);
+    }
+
+    @Override
+    public void writeTo(ByteBuffer data) {
+        super.writeTo(data);//TBD. This Fn needs work. Should never get called though.
+
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public String toString() {
+        return "V6StatsReply[" + ReflectionToStringBuilder.toString(this) + "]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6StatsRequest.java b/opendaylight/protocol_plugins/openflow/src/main/java/org/opendaylight/controller/protocol_plugin/openflow/vendorextension/v6extension/V6StatsRequest.java
new file mode 100644 (file)
index 0000000..26d0065
--- /dev/null
@@ -0,0 +1,156 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.openflow.protocol.statistics.OFVendorStatistics;
+import java.nio.ByteBuffer;
+
+
+/**
+ * This Class creates the OpenFlow Vendor Extension IPv6 Flow Stats Request 
+ * messages and also reads the Reply of a stats request message.
+ * 
+ */
+
+public class V6StatsRequest extends OFVendorStatistics {
+    private static final long serialVersionUID = 1L;
+    protected int msgsubtype;
+    protected short outPort;
+    protected short match_len;
+    protected byte tableId;
+
+    public static final int NICIRA_VENDOR_ID = 0x00002320; //Nicira ID
+    private static final int NXST_FLOW = 0x0; //Nicira Flow Stats Request Id
+
+    public V6StatsRequest() {
+        this.vendor = NICIRA_VENDOR_ID;
+        this.msgsubtype = NXST_FLOW;
+        this.match_len = 0;
+    }
+
+    /**
+     * @param None. Being set with local variable (TBD).
+     */
+    public void setVendorId() {
+        this.vendor = NICIRA_VENDOR_ID;
+    }
+
+    /**
+     * @return vendor id
+     */
+    public int getVendorId() {
+        return vendor;
+    }
+
+    /**
+     * @param None. Being set with local variable (TBD).
+     */
+    public void setMsgtype() {
+        this.msgsubtype = NXST_FLOW;
+    }
+
+    /**
+     * @return vendor_msgtype
+     */
+    public int getMsgtype() {
+        return msgsubtype;
+    }
+
+    /**
+     * @param outPort the outPort to set
+     */
+    public void setOutPort(short outPort) {
+        this.outPort = outPort;
+    }
+
+    /**
+     * @return the outPort
+     */
+    public short getOutPort() {
+        return outPort;
+    }
+
+    /**
+     * @param match_len the match_len to set
+     */
+    public void setMatchLen(short match_len) {
+        this.match_len = match_len;
+    }
+
+    /**
+     * @return the match_len
+     */
+    public short getMatchLen() {
+        return match_len;
+    }
+
+    /**
+     * @param tableId the tableId to set
+     */
+    public void setTableId(byte tableId) {
+        this.tableId = tableId;
+    }
+
+    /**
+     * @return the tableId
+     */
+    public byte getTableId() {
+        return tableId;
+    }
+
+    @Override
+    public int getLength() {
+        return 20;// 4(vendor)+4(msgsubtype)+4(pad)+2(outPort)+2(match_len)+1(tableid)+3(pad)
+    }
+
+    @Override
+    public void readFrom(ByteBuffer data) {
+        this.vendor = data.getInt();
+        this.msgsubtype = data.getInt();
+        data.getInt();//pad 4 bytes
+        this.outPort = data.getShort();
+        this.match_len = data.getShort();
+        this.tableId = data.get();
+        for (int i = 0; i < 3; i++)
+            data.get();//pad byte
+
+    }
+
+    @Override
+    public void writeTo(ByteBuffer data) {
+        data.putInt(this.vendor);
+        data.putInt(this.msgsubtype);
+        data.putInt((int) 0x0);//pad0
+        data.putShort(this.outPort);
+        data.putShort(this.match_len);
+        data.put(this.tableId);
+        for (int i = 0; i < 3; i++)
+            data.put((byte) 0x0);//pad byte
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public String toString() {
+        return "V6StatsRequest[" + ReflectionToStringBuilder.toString(this)
+                + "]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+}
diff --git a/opendaylight/protocol_plugins/openflow/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java b/opendaylight/protocol_plugins/openflow/src/test/java/org/opendaylight/controller/protocol_plugin/openflow/internal/FlowProgrammerServiceTest.java
new file mode 100644 (file)
index 0000000..2972233
--- /dev/null
@@ -0,0 +1,359 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.protocol_plugin.openflow.internal;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.protocol_plugin.openflow.internal.FlowConverter;
+import org.opendaylight.controller.protocol_plugin.openflow.vendorextension.v6extension.V6Match;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.action.OFAction;
+
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.Flood;
+import org.opendaylight.controller.sal.action.FloodAll;
+import org.opendaylight.controller.sal.action.HwPath;
+import org.opendaylight.controller.sal.action.Loopback;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.PopVlan;
+import org.opendaylight.controller.sal.action.SetDlDst;
+import org.opendaylight.controller.sal.action.SetDlSrc;
+import org.opendaylight.controller.sal.action.SetNwDst;
+import org.opendaylight.controller.sal.action.SetNwSrc;
+import org.opendaylight.controller.sal.action.SetNwTos;
+import org.opendaylight.controller.sal.action.SetTpDst;
+import org.opendaylight.controller.sal.action.SetTpSrc;
+import org.opendaylight.controller.sal.action.SetVlanId;
+import org.opendaylight.controller.sal.action.SwPath;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.IPProtocols;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+public class FlowProgrammerServiceTest {
+
+    @Test
+    public void testSALtoOFFlowConverter() throws UnknownHostException {
+        Node node = NodeCreator.createOFNode(1000l);
+        NodeConnector port = NodeConnectorCreator.createNodeConnector(
+                (short) 24, node);
+        NodeConnector oport = NodeConnectorCreator.createNodeConnector(
+                (short) 30, node);
+        byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
+                (byte) 0x9a, (byte) 0xbc };
+        byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d,
+                (byte) 0x5e, (byte) 0x6f };
+        InetAddress srcIP = InetAddress.getByName("172.28.30.50");
+        InetAddress dstIP = InetAddress.getByName("171.71.9.52");
+        InetAddress ipMask = InetAddress.getByName("255.255.255.0");
+        short ethertype = EtherTypes.IPv4.shortValue();
+        short vlan = (short) 27;
+        byte vlanPr = 3;
+        Byte tos = 4;
+        byte proto = IPProtocols.TCP.byteValue();
+        short src = (short) 55000;
+        short dst = 80;
+
+        /*
+         * Create a SAL Flow aFlow
+         */
+        Match match = new Match();
+        match.setField(MatchType.IN_PORT, port);
+        match.setField(MatchType.DL_SRC, srcMac);
+        match.setField(MatchType.DL_DST, dstMac);
+        match.setField(MatchType.DL_TYPE, ethertype);
+        match.setField(MatchType.DL_VLAN, vlan);
+        match.setField(MatchType.DL_VLAN_PR, vlanPr);
+        match.setField(MatchType.NW_SRC, srcIP, ipMask);
+        match.setField(MatchType.NW_DST, dstIP, ipMask);
+        match.setField(MatchType.NW_TOS, tos);
+        match.setField(MatchType.NW_PROTO, proto);
+        match.setField(MatchType.TP_SRC, src);
+        match.setField(MatchType.TP_DST, dst);
+
+        Assert.assertTrue(match.isIPv4());
+
+        List<Action> actions = new ArrayList<Action>();
+        // Setting all the actions supported by of
+        actions.add(new PopVlan());
+        actions.add(new Output(oport));
+        actions.add(new Flood());
+        actions.add(new FloodAll());
+        actions.add(new SwPath());
+        actions.add(new HwPath());
+        actions.add(new Loopback());
+        byte mac[] = { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5 };
+        actions.add(new SetDlSrc(mac));
+        actions.add(new SetDlDst(mac));
+        actions.add(new SetNwSrc(dstIP));
+        actions.add(new SetNwDst(srcIP));
+        actions.add(new SetNwTos(3));
+        actions.add(new SetTpSrc(10));
+        actions.add(new SetTpDst(20));
+        actions.add(new SetVlanId(200));
+
+        Flow aFlow = new Flow(match, actions);
+
+        /*
+         * Convert the SAL aFlow to OF Flow
+         */
+        FlowConverter salToOF = new FlowConverter(aFlow);
+        OFMatch ofMatch = salToOF.getOFMatch();
+        List<OFAction> ofActions = salToOF.getOFActions();
+
+        /*
+         * Convert the OF Flow to SAL Flow bFlow
+         */
+        FlowConverter ofToSal = new FlowConverter(ofMatch, ofActions);
+        Flow bFlow = ofToSal.getFlow(node);
+        Match bMatch = bFlow.getMatch();
+        List<Action> bActions = bFlow.getActions();
+
+        /*
+         * Verify the converted SAL flow bFlow is equivalent to the original SAL Flow
+         */
+        Assert.assertTrue(((NodeConnector) match.getField(MatchType.IN_PORT)
+                .getValue()).equals(((NodeConnector) bMatch.getField(
+                MatchType.IN_PORT).getValue())));
+        Assert.assertTrue(Arrays.equals((byte[]) match.getField(
+                MatchType.DL_SRC).getValue(), (byte[]) bMatch.getField(
+                MatchType.DL_SRC).getValue()));
+        Assert.assertTrue(Arrays.equals((byte[]) match.getField(
+                MatchType.DL_DST).getValue(), (byte[]) bMatch.getField(
+                MatchType.DL_DST).getValue()));
+        Assert
+                .assertTrue(((Short) match.getField(MatchType.DL_TYPE)
+                        .getValue()).equals((Short) bMatch.getField(
+                        MatchType.DL_TYPE).getValue()));
+        Assert
+                .assertTrue(((Short) match.getField(MatchType.DL_VLAN)
+                        .getValue()).equals((Short) bMatch.getField(
+                        MatchType.DL_VLAN).getValue()));
+        Assert.assertTrue(((Byte) match.getField(MatchType.DL_VLAN_PR)
+                .getValue()).equals((Byte) bMatch
+                .getField(MatchType.DL_VLAN_PR).getValue()));
+        Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_SRC)
+                .getValue()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_SRC).getValue()));
+        Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_SRC)
+                .getMask()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_SRC).getMask()));
+        Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_DST)
+                .getValue()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_DST).getValue()));
+        Assert.assertTrue(((InetAddress) match.getField(MatchType.NW_DST)
+                .getMask()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_DST).getMask()));
+        Assert
+                .assertTrue(((Byte) match.getField(MatchType.NW_PROTO)
+                        .getValue()).equals((Byte) bMatch.getField(
+                        MatchType.NW_PROTO).getValue()));
+        Assert.assertTrue(((Byte) match.getField(MatchType.NW_TOS).getValue())
+                .equals((Byte) bMatch.getField(MatchType.NW_TOS).getValue()));
+        Assert.assertTrue(((Short) match.getField(MatchType.TP_SRC).getValue())
+                .equals((Short) bMatch.getField(MatchType.TP_SRC).getValue()));
+        Assert.assertTrue(((Short) match.getField(MatchType.TP_DST).getValue())
+                .equals((Short) bMatch.getField(MatchType.TP_DST).getValue()));
+
+        // FlowConverter parses and sets the actions in the same order for sal match and of match
+        for (short i = 0; i < actions.size(); i++) {
+            Assert.assertTrue(actions.get(i).equals(bActions.get(i)));
+        }
+    }
+
+    @Test
+    public void testV6toSALFlowConversion() throws Exception {
+        Node node = NodeCreator.createOFNode(12l);
+        NodeConnector port = NodeConnectorCreator.createNodeConnector(
+                (short) 34, node);
+        NodeConnector oport = NodeConnectorCreator.createNodeConnector(
+                (short) 30, node);
+        byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
+                (byte) 0x9a, (byte) 0xbc };
+        byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d,
+                (byte) 0x5e, (byte) 0x6f };
+        InetAddress srcIP = InetAddress
+                .getByName("2001:420:281:1004:407a:57f4:4d15:c355");
+        InetAddress dstIP = InetAddress
+                .getByName("2001:420:281:1004:e123:e688:d655:a1b0");
+        InetAddress ipMask = InetAddress
+                .getByName("ffff:ffff:ffff:ffff:0:0:0:0");
+        short ethertype = EtherTypes.IPv6.shortValue();
+        short vlan = (short) 27;
+        byte vlanPr = 3;
+        Byte tos = 4;
+        byte proto = IPProtocols.TCP.byteValue();
+        short src = (short) 55000;
+        short dst = 80;
+
+        /*
+         * Create a SAL Flow aFlow
+         */
+        Match aMatch = new Match();
+
+        aMatch.setField(MatchType.IN_PORT, port);
+        aMatch.setField(MatchType.DL_SRC, srcMac);
+        aMatch.setField(MatchType.DL_DST, dstMac);
+        aMatch.setField(MatchType.DL_TYPE, ethertype);
+        aMatch.setField(MatchType.DL_VLAN, vlan);
+        aMatch.setField(MatchType.DL_VLAN_PR, vlanPr);
+        aMatch.setField(MatchType.NW_SRC, srcIP, ipMask);
+        aMatch.setField(MatchType.NW_DST, dstIP, ipMask);
+        aMatch.setField(MatchType.NW_TOS, tos);
+        aMatch.setField(MatchType.NW_PROTO, proto);
+        aMatch.setField(MatchType.TP_SRC, src);
+        aMatch.setField(MatchType.TP_DST, dst);
+
+        Assert.assertTrue(aMatch.isIPv6());
+
+        List<Action> actions = new ArrayList<Action>();
+        // Setting all the actions supported by of for v6
+        actions.add(new PopVlan());
+        actions.add(new Output(oport));
+        actions.add(new Flood());
+        actions.add(new FloodAll());
+        actions.add(new SwPath());
+        actions.add(new HwPath());
+        actions.add(new Loopback());
+        byte mac[] = { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5 };
+        actions.add(new SetDlSrc(mac));
+        actions.add(new SetDlDst(mac));
+        //actions.add(new SetNwSrc(dstIP)); Nicira extensions do not provide IPv6 match addresses change
+        //actions.add(new SetNwDst(srcIP));
+        actions.add(new SetNwTos(3));
+        actions.add(new SetTpSrc(10));
+        actions.add(new SetTpDst(65535));
+        actions.add(new SetVlanId(200));
+
+        Flow aFlow = new Flow(aMatch, actions);
+
+        /*
+         * Convert the SAL aFlow to OF Flow
+         */
+        FlowConverter salToOF = new FlowConverter(aFlow);
+        V6Match v6Match = (V6Match) salToOF.getOFMatch();
+        List<OFAction> ofActions = salToOF.getOFActions();
+
+        /*
+         * Convert the OF Flow to SAL Flow bFlow
+         */
+        FlowConverter ofToSal = new FlowConverter(v6Match, ofActions);
+        Flow bFlow = ofToSal.getFlow(node);
+        Match bMatch = bFlow.getMatch();
+        List<Action> bActions = bFlow.getActions();
+
+        /*
+         * Verify the converted SAL flow bFlow is equivalent to the original SAL Flow
+         */
+        Assert.assertTrue(((NodeConnector) aMatch.getField(MatchType.IN_PORT)
+                .getValue()).equals(((NodeConnector) bMatch.getField(
+                MatchType.IN_PORT).getValue())));
+        Assert.assertTrue(Arrays.equals((byte[]) aMatch.getField(
+                MatchType.DL_SRC).getValue(), (byte[]) bMatch.getField(
+                MatchType.DL_SRC).getValue()));
+        Assert.assertTrue(Arrays.equals((byte[]) aMatch.getField(
+                MatchType.DL_DST).getValue(), (byte[]) bMatch.getField(
+                MatchType.DL_DST).getValue()));
+        Assert.assertTrue(((Short) aMatch.getField(MatchType.DL_TYPE)
+                .getValue()).equals((Short) bMatch.getField(MatchType.DL_TYPE)
+                .getValue()));
+        Assert.assertTrue(((Short) aMatch.getField(MatchType.DL_VLAN)
+                .getValue()).equals((Short) bMatch.getField(MatchType.DL_VLAN)
+                .getValue()));
+        Assert.assertTrue(((Byte) aMatch.getField(MatchType.DL_VLAN_PR)
+                .getValue()).equals((Byte) bMatch
+                .getField(MatchType.DL_VLAN_PR).getValue()));
+        Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_SRC)
+                .getValue()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_SRC).getValue()));
+        Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_SRC)
+                .getMask()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_SRC).getMask()));
+        Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_DST)
+                .getValue()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_DST).getValue()));
+        Assert.assertTrue(((InetAddress) aMatch.getField(MatchType.NW_DST)
+                .getMask()).equals((InetAddress) bMatch.getField(
+                MatchType.NW_DST).getMask()));
+        Assert.assertTrue(((Byte) aMatch.getField(MatchType.NW_PROTO)
+                .getValue()).equals((Byte) bMatch.getField(MatchType.NW_PROTO)
+                .getValue()));
+        Assert.assertTrue(((Byte) aMatch.getField(MatchType.NW_TOS).getValue())
+                .equals((Byte) bMatch.getField(MatchType.NW_TOS).getValue()));
+        Assert
+                .assertTrue(((Short) aMatch.getField(MatchType.TP_SRC)
+                        .getValue()).equals((Short) bMatch.getField(
+                        MatchType.TP_SRC).getValue()));
+        Assert
+                .assertTrue(((Short) aMatch.getField(MatchType.TP_DST)
+                        .getValue()).equals((Short) bMatch.getField(
+                        MatchType.TP_DST).getValue()));
+
+        // FlowConverter parses and sets the actions in the same order for sal match and of match
+        for (short i = 0; i < actions.size(); i++) {
+            Assert.assertTrue(actions.get(i).equals(bActions.get(i)));
+        }
+    }
+
+    @Test
+    public void testV6MatchToSALMatchToV6MatchConversion()
+            throws UnknownHostException {
+        NodeConnector port = NodeConnectorCreator.createNodeConnector(
+                (short) 24, NodeCreator.createOFNode(6l));
+        byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
+                (byte) 0x9a, (byte) 0xbc };
+        byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d,
+                (byte) 0x5e, (byte) 0x6f };
+        InetAddress srcIP = InetAddress
+                .getByName("2001:420:281:1004:407a:57f4:4d15:c355");
+        InetAddress dstIP = InetAddress
+                .getByName("2001:420:281:1004:e123:e688:d655:a1b0");
+        InetAddress ipMask = null;//InetAddress.getByName("ffff:ffff:ffff:ffff:0:0:0:0");
+        short ethertype = EtherTypes.IPv6.shortValue();
+        short vlan = (short) 27;
+        byte vlanPr = 3;
+        Byte tos = 4;
+        byte proto = IPProtocols.TCP.byteValue();
+        short src = (short) 55000;
+        short dst = 80;
+
+        /*
+         * Create a SAL Flow aFlow
+         */
+        Match aMatch = new Match();
+
+        aMatch.setField(MatchType.IN_PORT, port);
+        aMatch.setField(MatchType.DL_SRC, srcMac);
+        aMatch.setField(MatchType.DL_DST, dstMac);
+        aMatch.setField(MatchType.DL_TYPE, ethertype);
+        aMatch.setField(MatchType.DL_VLAN, vlan);
+        aMatch.setField(MatchType.DL_VLAN_PR, vlanPr);
+        aMatch.setField(MatchType.NW_SRC, srcIP, ipMask);
+        aMatch.setField(MatchType.NW_DST, dstIP, ipMask);
+        aMatch.setField(MatchType.NW_TOS, tos);
+        aMatch.setField(MatchType.NW_PROTO, proto);
+        aMatch.setField(MatchType.TP_SRC, src);
+        aMatch.setField(MatchType.TP_DST, dst);
+
+        Assert.assertTrue(aMatch.isIPv6());
+
+    }
+}
diff --git a/opendaylight/routing/dijkstra_implementation/pom.xml b/opendaylight/routing/dijkstra_implementation/pom.xml
new file mode 100644 (file)
index 0000000..6c394cf
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>routing.dijkstra_implementation</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+    <groupId>org.apache.felix</groupId>
+    <artifactId>maven-bundle-plugin</artifactId>
+    <version>2.3.6</version>
+    <extensions>true</extensions>
+    <configuration>
+      <instructions>
+        <Import-Package>
+          org.slf4j,
+          org.opendaylight.controller.sal.routing,
+          org.opendaylight.controller.sal.core,
+          org.opendaylight.controller.sal.topology,
+          org.opendaylight.controller.sal.utils,
+          org.opendaylight.controller.sal.reader,
+          org.apache.commons.collections15,
+          org.opendaylight.controller.switchmanager, 
+          edu.uci.ics.jung.graph,
+          edu.uci.ics.jung.algorithms.shortestpath,
+          edu.uci.ics.jung.graph.util,
+          org.apache.felix.dm,
+          org.junit;resolution:=optional
+        </Import-Package>
+        <Bundle-Activator>
+          org.opendaylight.controller.routing.dijkstra_implementation.internal.Activator
+        </Bundle-Activator>
+      </instructions>
+    </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>net.sf.jung2</artifactId>
+      <version>2.0.1-SNAPSHOT</version>
+    </dependency>
+   <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/routing/dijkstra_implementation/src/main/java/org/opendaylight/controller/routing/dijkstra_implementation/internal/Activator.java b/opendaylight/routing/dijkstra_implementation/src/main/java/org/opendaylight/controller/routing/dijkstra_implementation/internal/Activator.java
new file mode 100644 (file)
index 0000000..2137cda
--- /dev/null
@@ -0,0 +1,99 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.routing.dijkstra_implementation.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.apache.felix.dm.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.routing.IListenRoutingUpdates;
+import org.opendaylight.controller.sal.routing.IRouting;
+import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.sal.reader.IReadService;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+        logger.debug("routing.dijkstra_implementation INIT called!");
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { DijkstraImplementation.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(DijkstraImplementation.class)) {
+            // export the service
+            Dictionary<String, String> props = new Hashtable<String, String>();
+            props.put("salListenerName", "routing.Dijkstra");
+            c.setInterface(new String[] { IListenTopoUpdates.class.getName(),
+                    IRouting.class.getName() }, props);
+
+            // Now lets add a service dependency to make sure the
+            // provider of service exists
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IListenRoutingUpdates.class).setCallbacks(
+                    "setLIstenRoutingUpdates", "unsetLIstenRoutingUpdates")
+                    .setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManager.class).setCallbacks("setSwitchManager",
+                    "unsetSwitchManager").setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IReadService.class).setCallbacks("setReadService",
+                    "unsetReadService").setRequired(true));
+        }
+    }
+}
diff --git a/opendaylight/routing/dijkstra_implementation/src/main/java/org/opendaylight/controller/routing/dijkstra_implementation/internal/DijkstraImplementation.java b/opendaylight/routing/dijkstra_implementation/src/main/java/org/opendaylight/controller/routing/dijkstra_implementation/internal/DijkstraImplementation.java
new file mode 100644 (file)
index 0000000..386b226
--- /dev/null
@@ -0,0 +1,425 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * @file   DijkstraImplementation.java
+ *
+ *
+ * @brief  Implementation of a routing engine using
+ * dijkstra. Implementation of dijkstra come from Jung2 library
+ *
+ */
+package org.opendaylight.controller.routing.dijkstra_implementation.internal;
+
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Path;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+import org.opendaylight.controller.sal.reader.IReadService;
+import org.opendaylight.controller.sal.routing.IListenRoutingUpdates;
+import org.opendaylight.controller.sal.routing.IRouting;
+import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import java.lang.Exception;
+import java.lang.IllegalArgumentException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.collections15.Transformer;
+
+public class DijkstraImplementation implements IRouting, IListenTopoUpdates {
+    private static Logger log = LoggerFactory
+            .getLogger(DijkstraImplementation.class);
+    private ConcurrentMap<Short, Graph<Node, Edge>> topologyBWAware;
+    private ConcurrentMap<Short, DijkstraShortestPath<Node, Edge>> sptBWAware;
+    DijkstraShortestPath<Node, Edge> mtp; //Max Throughput Path
+    private Set<IListenRoutingUpdates> routingAware;
+    private ISwitchManager switchManager;
+    private IReadService readService;
+    private static final long DEFAULT_LINK_SPEED = Bandwidth.BW1Gbps;
+
+    public void setLIstenRoutingUpdates(IListenRoutingUpdates i) {
+        if (this.routingAware == null) {
+            this.routingAware = new HashSet<IListenRoutingUpdates>();
+        }
+        if (this.routingAware != null) {
+            log.debug("Adding routingAware listener");
+            this.routingAware.add(i);
+        }
+    }
+
+    public void unsetRoutingUpdates(IListenRoutingUpdates i) {
+        if (this.routingAware == null) {
+            return;
+        }
+        log.debug("Removing routingAware listener");
+        this.routingAware.remove(i);
+        if (this.routingAware.isEmpty()) {
+            // We don't have any listener lets dereference
+            this.routingAware = null;
+        }
+    }
+
+    @SuppressWarnings( { "unchecked", "rawtypes" })
+    public DijkstraImplementation() {
+        this.topologyBWAware = (ConcurrentMap<Short, Graph<Node, Edge>>) new ConcurrentHashMap();
+        this.sptBWAware = (ConcurrentMap<Short, DijkstraShortestPath<Node, Edge>>) new ConcurrentHashMap();
+        // Now create the default topology, which doesn't consider the
+        // BW, also create the corresponding Dijkstra calculation
+        Graph<Node, Edge> g = new SparseMultigraph();
+        Short sZero = Short.valueOf((short) 0);
+        this.topologyBWAware.put(sZero, g);
+        this.sptBWAware.put(sZero, new DijkstraShortestPath(g));
+        // Topologies for other BW will be added on a needed base
+    }
+
+    @Override
+    public synchronized void initMaxThroughput(
+            final Map<Edge, Number> EdgeWeightMap) {
+        if (mtp != null) {
+            log.error("Max Throughput Dijkstra is already enabled!");
+            return;
+        }
+        Transformer<Edge, ? extends Number> mtTransformer = null;
+        if (EdgeWeightMap == null) {
+            mtTransformer = new Transformer<Edge, Double>() {
+                public Double transform(Edge e) {
+                    if (switchManager == null) {
+                        log.error("switchManager is null");
+                        return (double) -1;
+                    }
+                    NodeConnector srcNC = e.getTailNodeConnector();
+                    NodeConnector dstNC = e.getHeadNodeConnector();
+                    if ((srcNC == null) || (dstNC == null)) {
+                        log.error("srcNC:{} or dstNC:{} is null", srcNC, dstNC);
+                        return (double) -1;
+                    }
+                    Bandwidth bwSrc = (Bandwidth) switchManager
+                            .getNodeConnectorProp(srcNC,
+                                    Bandwidth.BandwidthPropName);
+                    Bandwidth bwDst = (Bandwidth) switchManager
+                            .getNodeConnectorProp(dstNC,
+                                    Bandwidth.BandwidthPropName);
+
+                    if ((bwSrc == null) || (bwDst == null)) {
+                        log.error("bwSrc:{} or bwDst:{} is null", bwSrc, bwDst);
+                        return (double) -1;
+                    }
+
+                    long srcLinkSpeed = bwSrc.getValue();
+                    if (srcLinkSpeed == 0) {
+                        log.trace("Edge {}: srcLinkSpeed is 0. Setting to {}!",
+                                e, DEFAULT_LINK_SPEED);
+                        srcLinkSpeed = DEFAULT_LINK_SPEED;
+                    }
+
+                    long dstLinkSpeed = bwDst.getValue();
+                    if (dstLinkSpeed == 0) {
+                        log.trace("Edge {}: dstLinkSpeed is 0. Setting to {}!",
+                                e, DEFAULT_LINK_SPEED);
+                        dstLinkSpeed = DEFAULT_LINK_SPEED;
+                    }
+
+                    long avlSrcThruPut = srcLinkSpeed
+                            - readService.getTransmitRate(srcNC);
+                    long avlDstThruPut = dstLinkSpeed
+                            - readService.getTransmitRate(dstNC);
+
+                    //Use lower of the 2 available thruput as the available thruput
+                    long avlThruPut = avlSrcThruPut < avlDstThruPut ? avlSrcThruPut
+                            : avlDstThruPut;
+
+                    if (avlThruPut <= 0) {
+                        log
+                                .trace(
+                                        "Edge {}: Available Throughput {} is Zero/Negative",
+                                        e, avlThruPut);
+                        return (double) -1;
+                    }
+                    return (double) (Bandwidth.BW1Pbps / avlThruPut);
+                }
+            };
+        } else {
+            mtTransformer = new Transformer<Edge, Number>() {
+                public Number transform(Edge e) {
+                    return EdgeWeightMap.get(e);
+                }
+            };
+        }
+        Short baseBW = Short.valueOf((short) 0);
+        //Initialize mtp also using the default topo
+        Graph<Node, Edge> g = this.topologyBWAware.get(baseBW);
+        if (g == null) {
+            log.error("Default Topology Graph is null");
+            return;
+        }
+        mtp = new DijkstraShortestPath<Node, Edge>(g, mtTransformer);
+    }
+
+    @Override
+    public Path getRoute(Node src, Node dst) {
+        if (src == null || dst == null) {
+            return null;
+        }
+        return getRoute(src, dst, (short) 0);
+    }
+
+    @Override
+    public synchronized Path getMaxThroughputRoute(Node src, Node dst) {
+        if (mtp == null) {
+            log
+                    .error("Max Throughput Path Calculation has not been Initialized!");
+            return null;
+        }
+
+        List<Edge> path;
+        try {
+            path = mtp.getMaxThroughputPath(src, dst);
+        } catch (IllegalArgumentException ie) {
+            log.debug("A vertex is yet not known between " + src.toString()
+                    + " " + dst.toString());
+            return null;
+        }
+        Path res;
+        try {
+            res = new Path(path);
+        } catch (ConstructionException e) {
+            log.debug("A vertex is yet not known between " + src.toString()
+                    + " " + dst.toString());
+            return null;
+        }
+        return res;
+    }
+
+    @Override
+    public synchronized Path getRoute(Node src, Node dst, Short Bw) {
+        DijkstraShortestPath<Node, Edge> spt = this.sptBWAware.get(Bw);
+        if (spt == null)
+            return null;
+        List<Edge> path;
+        try {
+            path = spt.getPath(src, dst);
+        } catch (IllegalArgumentException ie) {
+            log.debug("A vertex is yet not known between " + src.toString()
+                    + " " + dst.toString());
+            return null;
+        }
+        Path res;
+        try {
+            res = new Path(path);
+        } catch (ConstructionException e) {
+            log.debug("A vertex is yet not known between " + src.toString()
+                    + " " + dst.toString());
+            return null;
+        }
+        return res;
+    }
+
+    @Override
+    public synchronized void clear() {
+        DijkstraShortestPath<Node, Edge> spt;
+        for (Short bw : this.sptBWAware.keySet()) {
+            spt = this.sptBWAware.get(bw);
+            if (spt != null) {
+                spt.reset();
+            }
+        }
+        clearMaxThroughput();
+    }
+
+    @Override
+    public synchronized void clearMaxThroughput() {
+        if (mtp != null) {
+            mtp.reset(); //reset maxthruput path
+        }
+    }
+
+    @SuppressWarnings( { "rawtypes", "unchecked" })
+    private synchronized boolean updateTopo(Edge edge, Short bw, boolean added) {
+        Graph<Node, Edge> topo = this.topologyBWAware.get(bw);
+        DijkstraShortestPath<Node, Edge> spt = this.sptBWAware.get(bw);
+        boolean edgePresentInGraph = false;
+        Short baseBW = Short.valueOf((short) 0);
+
+        if (topo == null) {
+            // Create topology for this BW
+            Graph<Node, Edge> g = new SparseMultigraph();
+            this.topologyBWAware.put(bw, g);
+            topo = this.topologyBWAware.get(bw);
+            this.sptBWAware.put(bw, new DijkstraShortestPath(g));
+            spt = this.sptBWAware.get(bw);
+        }
+
+        if (topo != null) {
+            NodeConnector src = edge.getTailNodeConnector();
+            NodeConnector dst = edge.getHeadNodeConnector();
+            if (spt == null) {
+                spt = new DijkstraShortestPath(topo);
+                this.sptBWAware.put(bw, spt);
+            }
+
+            if (added) {
+                // Make sure the vertex are there before adding the edge
+                topo.addVertex(src.getNode());
+                topo.addVertex(dst.getNode());
+                // Add the link between
+                edgePresentInGraph = topo.containsEdge(edge);
+                if (edgePresentInGraph == false) {
+                    try {
+                        topo.addEdge(new Edge(src, dst), src
+                                .getNode(), dst
+                                .getNode(), EdgeType.DIRECTED);
+                    } catch (ConstructionException e) {
+                        e.printStackTrace();
+                        return edgePresentInGraph;
+                    }
+                }
+            } else {
+                //Remove the edge
+                try {
+                    topo.removeEdge(new Edge(src, dst));
+                } catch (ConstructionException e) {
+                    e.printStackTrace();
+                    return edgePresentInGraph;
+                }
+
+                // If the src and dst vertex don't have incoming or
+                // outgoing links we can get ride of them
+                if (topo.containsVertex(src.getNode())
+                        && topo.inDegree(src.getNode()) == 0
+                        && topo.outDegree(src.getNode()) == 0) {
+                    log.debug("Removing vertex " + src);
+                    topo.removeVertex(src.getNode());
+                }
+
+                if (topo.containsVertex(dst.getNode())
+                        && topo.inDegree(dst.getNode()) == 0
+                        && topo.outDegree(dst.getNode()) == 0) {
+                    log.debug("Removing vertex " + dst);
+                    topo.removeVertex(dst.getNode());
+                }
+            }
+            spt.reset();
+            if (bw.equals(baseBW)) {
+                clearMaxThroughput();
+            }
+        } else {
+            log.error("Cannot find topology for BW " + bw
+                    + " this is unexpected!");
+        }
+        return edgePresentInGraph;
+    }
+
+    @Override
+    public void edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
+        String srcType = null;
+        String dstType = null;
+
+        if (e == null || type == null) {
+            log.error("Edge or Update type are null!");
+            return;
+        } else {
+            srcType = e.getTailNodeConnector().getType();
+            dstType = e.getHeadNodeConnector().getType();
+
+            if (srcType.equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+                log.debug("Skip updates for {}", e);
+                return;
+            }
+
+            if (dstType.equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) {
+                log.debug("Skip updates for {}", e);
+                return;
+            }
+        }
+
+        Bandwidth bw = new Bandwidth(0);
+        boolean newEdge = false;
+        if (props != null)
+            props.remove(bw);
+
+        log.debug("edgeUpdate: " + e.toString() + " bw: " + bw.getValue());
+
+        Short baseBW = Short.valueOf((short) 0);
+        boolean add = (type == UpdateType.ADDED) ? true : false;
+        // Update base topo
+        newEdge = !updateTopo(e, baseBW, add);
+        if (newEdge == true) {
+            if (bw.getValue() != baseBW) {
+                // Update BW topo
+                updateTopo(e, (short) bw.getValue(), add);
+            }
+            if (this.routingAware != null) {
+                log.info("Invoking routingAware listeners");
+                for (IListenRoutingUpdates ra : this.routingAware) {
+                    try {
+                        ra.recalculateDone();
+                    } catch (Exception ex) {
+                        log.error("Exception on routingAware listener call", e);
+                    }
+                }
+            }
+        }
+    }
+
+    public void startUp() {
+        log.debug(this.getClass().getName() + ":startUp Method Called");
+    }
+
+    public void shutDown() {
+        log.debug(this.getClass().getName() + ":shutDown Method Called");
+    }
+
+    @Override
+    public void edgeOverUtilized(Edge edge) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void edgeUtilBackToNormal(Edge edge) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setSwitchManager(ISwitchManager switchManager) {
+        this.switchManager = switchManager;
+    }
+
+    public void unsetSwitchManager(ISwitchManager switchManager) {
+        if (this.switchManager == switchManager) {
+            this.switchManager = null;
+        }
+    }
+
+    public void setReadService(IReadService readService) {
+        this.readService = readService;
+    }
+
+    public void unsetReadService(IReadService readService) {
+        if (this.readService == readService) {
+            this.readService = null;
+        }
+    }
+}
diff --git a/opendaylight/routing/dijkstra_implementation/src/main/resources/OSGI-INF/component-factory.xml b/opendaylight/routing/dijkstra_implementation/src/main/resources/OSGI-INF/component-factory.xml
new file mode 100644 (file)
index 0000000..d71bef8
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
+ activate="factoryStartUp" deactivate="factoryShutDown"
+ factory="routing.dijkstra_implementation.factory" name="routing.dijkstra_implementation.ComponentFactory">
+   <implementation class="org.opendaylight.controller.routing.dijkstra_implementation.internal.DijkstraImplementation"/>
+   <service>
+      <provide interface="org.osgi.service.component.ComponentFactory"/>
+   </service>
+</scr:component>
diff --git a/opendaylight/routing/dijkstra_implementation/src/main/resources/OSGI-INF/component.xml b/opendaylight/routing/dijkstra_implementation/src/main/resources/OSGI-INF/component.xml
new file mode 100644 (file)
index 0000000..f530768
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
+               activate="startUp"
+               deactivate="shutDown"
+               name="routing.dijkstra_implementation.Component">
+  <implementation class="org.opendaylight.controller.routing.dijkstra_implementation.internal.DijkstraImplementation"/>
+
+  <service>
+    <provide interface="org.opendaylight.controller.sal.routing.IONERouting"/>
+    <provide interface="org.opendaylight.controller.sal.topology.IONETopologyBwAware"/>
+  </service>
+  <reference name="ONERoutingAware"
+             bind="setONERoutingAware"
+             unbind="unsetONERoutingAware"
+             cardinality="0..n"
+             policy="dynamic"
+             interface="org.opendaylight.controller.sal.routing.IONERoutingAware"/>
+</scr:component>
diff --git a/opendaylight/routing/dijkstra_implementation/src/test/java/org/opendaylight/controller/routing/dijkstra_implementation/DijkstraTest.java b/opendaylight/routing/dijkstra_implementation/src/test/java/org/opendaylight/controller/routing/dijkstra_implementation/DijkstraTest.java
new file mode 100644 (file)
index 0000000..45c75f1
--- /dev/null
@@ -0,0 +1,215 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.routing.dijkstra_implementation;
+
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Path;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.routing.dijkstra_implementation.internal.DijkstraImplementation;
+
+public class DijkstraTest {
+    @Test
+    public void testSinglePathRouteNoBw() {
+        DijkstraImplementation imp = new DijkstraImplementation();
+        Node node1 = NodeCreator.createOFNode((long) 1);
+        Node node2 = NodeCreator.createOFNode((long) 2);
+        Node node3 = NodeCreator.createOFNode((long) 3);
+        NodeConnector nc11 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node1);
+        NodeConnector nc21 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node2);
+        Edge edge1 = null;
+        try {
+            edge1 = new Edge(nc11, nc21);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge1, UpdateType.ADDED, props);
+        NodeConnector nc22 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node2);
+        NodeConnector nc31 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node3);
+        Edge edge2 = null;
+        try {
+            edge2 = new Edge(nc22, nc31);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props2 = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge2, UpdateType.ADDED, props2);
+        Path res = imp.getRoute(node1, node3);
+
+        List<Edge> expectedPath = (List<Edge>) new LinkedList<Edge>();
+        expectedPath.add(0, edge1);
+        expectedPath.add(1, edge2);
+        Path expectedRes = null;
+        try {
+            expectedRes = new Path(expectedPath);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        if (!res.equals(expectedRes)) {
+            System.out.println("Actual Res is " + res);
+            System.out.println("Expected Res is " + expectedRes);
+        }
+        Assert.assertTrue(res.equals(expectedRes));
+    }
+
+    @Test
+    public void testShortestPathRouteNoBw() {
+        DijkstraImplementation imp = new DijkstraImplementation();
+        Node node1 = NodeCreator.createOFNode((long) 1);
+        Node node2 = NodeCreator.createOFNode((long) 2);
+        Node node3 = NodeCreator.createOFNode((long) 3);
+        NodeConnector nc11 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node1);
+        NodeConnector nc21 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node2);
+        Edge edge1 = null;
+        try {
+            edge1 = new Edge(nc11, nc21);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge1, UpdateType.ADDED, props);
+
+        NodeConnector nc22 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node2);
+        NodeConnector nc31 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node3);
+        Edge edge2 = null;
+        try {
+            edge2 = new Edge(nc22, nc31);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props2 = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge2, UpdateType.ADDED, props2);
+
+        NodeConnector nc12 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node1);
+        NodeConnector nc32 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node3);
+        Edge edge3 = null;
+        try {
+            edge3 = new Edge(nc12, nc32);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props3 = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge3, UpdateType.ADDED, props3);
+
+        Path res = imp.getRoute(node1, node3);
+
+        List<Edge> expectedPath = (List<Edge>) new LinkedList<Edge>();
+        expectedPath.add(0, edge3);
+        Path expectedRes = null;
+        try {
+            expectedRes = new Path(expectedPath);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        if (!res.equals(expectedRes)) {
+            System.out.println("Actual Res is " + res);
+            System.out.println("Expected Res is " + expectedRes);
+        }
+        Assert.assertTrue(res.equals(expectedRes));
+    }
+
+    @Test
+    public void testShortestPathRouteNoBwAfterLinkDelete() {
+        DijkstraImplementation imp = new DijkstraImplementation();
+        Node node1 = NodeCreator.createOFNode((long) 1);
+        Node node2 = NodeCreator.createOFNode((long) 2);
+        Node node3 = NodeCreator.createOFNode((long) 3);
+        NodeConnector nc11 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node1);
+        NodeConnector nc21 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node2);
+        Edge edge1 = null;
+        try {
+            edge1 = new Edge(nc11, nc21);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge1, UpdateType.ADDED, props);
+
+        NodeConnector nc22 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node2);
+        NodeConnector nc31 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node3);
+        Edge edge2 = null;
+        try {
+            edge2 = new Edge(nc22, nc31);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props2 = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge2, UpdateType.ADDED, props2);
+
+        NodeConnector nc12 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node1);
+        NodeConnector nc32 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node3);
+        Edge edge3 = null;
+        try {
+            edge3 = new Edge(nc12, nc32);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        Set<Property> props3 = new HashSet<Property>();
+        props.add(new Bandwidth(0));
+        imp.edgeUpdate(edge3, UpdateType.ADDED, props3);
+
+        imp.edgeUpdate(edge3, UpdateType.REMOVED, props3);
+
+        Path res = imp.getRoute(node1, node3);
+        List<Edge> expectedPath = (List<Edge>) new LinkedList<Edge>();
+        expectedPath.add(0, edge1);
+        expectedPath.add(1, edge2);
+        Path expectedRes = null;
+        try {
+            expectedRes = new Path(expectedPath);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        if (!res.equals(expectedRes)) {
+            System.out.println("Actual Res is " + res);
+            System.out.println("Expected Res is " + expectedRes);
+        }
+        Assert.assertTrue(res.equals(expectedRes));
+    }
+}
diff --git a/opendaylight/routing/dijkstra_implementation/src/test/java/org/opendaylight/controller/routing/dijkstra_implementation/MaxThruputTest.java b/opendaylight/routing/dijkstra_implementation/src/test/java/org/opendaylight/controller/routing/dijkstra_implementation/MaxThruputTest.java
new file mode 100644 (file)
index 0000000..1bab078
--- /dev/null
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.routing.dijkstra_implementation;
+
+import org.junit.Test;
+
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Path;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.opendaylight.controller.routing.dijkstra_implementation.internal.DijkstraImplementation;
+
+import org.opendaylight.controller.sal.core.UpdateType;
+
+public class MaxThruputTest {
+
+    Map<Edge, Number> LinkCostMap = new HashMap<Edge, Number>();
+
+    @Test
+    public void testMaxThruPut() {
+        DijkstraImplementation imp = new DijkstraImplementation();
+        Node node1 = NodeCreator.createOFNode((long) 1);
+        Node node2 = NodeCreator.createOFNode((long) 2);
+        Node node3 = NodeCreator.createOFNode((long) 3);
+        Node node4 = NodeCreator.createOFNode((long) 4);
+        Node node5 = NodeCreator.createOFNode((long) 5);
+        Node node6 = NodeCreator.createOFNode((long) 6);
+        NodeConnector nc11 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node1);
+        NodeConnector nc21 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node2);
+        NodeConnector nc31 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node3);
+        NodeConnector nc41 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node4);
+        NodeConnector nc51 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node5);
+        NodeConnector nc61 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 1, node6);
+        NodeConnector nc12 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node1);
+        NodeConnector nc22 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node2);
+        NodeConnector nc32 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node3);
+        NodeConnector nc42 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node4);
+        NodeConnector nc52 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node5);
+        NodeConnector nc62 = NodeConnectorCreator.createOFNodeConnector(
+                (short) 2, node6);
+
+        Edge edge1 = null;
+        try {
+            edge1 = new Edge(nc11, nc21);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge1, 10);
+        Edge edge2 = null;
+        try {
+            edge2 = new Edge(nc21, nc11);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge2, 10);
+
+        Edge edge3 = null;
+        try {
+            edge3 = new Edge(nc22, nc31);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge3, 30);
+        Edge edge4 = null;
+        try {
+            edge4 = new Edge(nc31, nc22);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge4, 30);
+
+        Edge edge5 = null;
+        try {
+            edge5 = new Edge(nc32, nc41);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge5, 10);
+        Edge edge6 = null;
+        try {
+            edge6 = new Edge(nc41, nc32);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge6, 10);
+
+        Edge edge7 = null;
+        try {
+            edge7 = new Edge(nc12, nc51);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge7, 20);
+        Edge edge8 = null;
+        try {
+            edge8 = new Edge(nc51, nc12);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge8, 20);
+
+        Edge edge9 = null;
+        try {
+            edge9 = new Edge(nc52, nc61);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge9, 20);
+        Edge edge10 = null;
+        try {
+            edge10 = new Edge(nc61, nc52);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge10, 20);
+
+        Edge edge11 = null;
+        try {
+            edge11 = new Edge(nc62, nc42);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge11, 20);
+        Edge edge12 = null;
+        try {
+            edge12 = new Edge(nc42, nc62);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        LinkCostMap.put(edge12, 20);
+
+        imp.edgeUpdate(edge1, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge2, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge3, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge4, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge5, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge6, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge7, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge8, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge9, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge10, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge11, UpdateType.ADDED, null);
+        imp.edgeUpdate(edge12, UpdateType.ADDED, null);
+
+        imp.initMaxThroughput(LinkCostMap);
+
+        Path res = imp.getMaxThroughputRoute(node1, node3);
+        System.out.println("Max Thruput Path between n1-n3: " + res);
+
+        List<Edge> expectedPath = (List<Edge>) new LinkedList<Edge>();
+        expectedPath.add(0, edge7);
+        expectedPath.add(1, edge9);
+        expectedPath.add(2, edge11);
+        expectedPath.add(3, edge6);
+
+        Path expectedRes = null;
+        try {
+            expectedRes = new Path(expectedPath);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        if (!res.equals(expectedRes)) {
+            System.out.println("Actual Res is " + res);
+            System.out.println("Expected Res is " + expectedRes);
+        }
+        Assert.assertTrue(res.equals(expectedRes));
+
+        res = imp.getRoute(node1, node3);
+        System.out.println("Shortest Path between n1-n3: " + res);
+        expectedPath.clear();
+        expectedPath.add(0, edge1);
+        expectedPath.add(1, edge3);
+
+        expectedRes = null;
+        try {
+            expectedRes = new Path(expectedPath);
+        } catch (ConstructionException e) {
+            e.printStackTrace();
+        }
+        if (!res.equals(expectedRes)) {
+            System.out.println("Actual Res is " + res);
+            System.out.println("Expected Res is " + expectedRes);
+        }
+        Assert.assertTrue(res.equals(expectedRes));
+    }
+}
index a4fc2e9ff714c5d75dc5a5aa2a6e646304871953..e376ba7a17341c72edd82ce93ca750988ca84f2c 100644 (file)
@@ -14,6 +14,7 @@ import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -130,7 +131,6 @@ public class Match implements Cloneable {
      *
      * @return the 32 bit long mask (Refer to {@code}org.opendaylight.controller.sal.match.MatchElement)
      */
-    @XmlElement
     public int getMatches() {
         return matches;
     }
@@ -144,6 +144,16 @@ public class Match implements Cloneable {
         return new ArrayList<MatchType>(fields.keySet());
     }
 
+    /**
+     * Returns the list of MatchFields the match is set for
+     *
+     * @return List of individual MatchField values. 
+     */
+    @XmlElement(name="matchField")
+    public List<MatchField> getMatchFields() {
+       return new ArrayList<MatchField>(fields.values());
+    }
+    
     /**
      * Returns whether this match is for an IPv6 flow
      */
index 19c365b29e041c338083e1ccc7f5e26a8e47509f..08d4fa698fc40eed8b6b3dba1f9076b54e5fedea 100644 (file)
@@ -9,6 +9,11 @@
 
 package org.opendaylight.controller.sal.match;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.opendaylight.controller.sal.utils.HexEncode;
@@ -18,17 +23,22 @@ import org.slf4j.LoggerFactory;
 /**
  * Represents the generic matching field
  *
- *
- *
  */
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+
 public class MatchField implements Cloneable {
     private static final Logger logger = LoggerFactory
             .getLogger(MatchField.class);
-    private MatchType type; // the field we want to match
+       private MatchType type; // the field we want to match
     private Object value; // the value of the field we want to match
     private Object mask; // the value of the mask we want to match on the specified field
     private transient boolean isValid;
 
+    // To satisfy JAXB
+    private MatchField() {
+    }
     /**
      * Mask based match constructor
      *
@@ -64,6 +74,11 @@ public class MatchField implements Cloneable {
     public Object getValue() {
         return value;
     }
+    
+    @XmlElement(name="value")
+    private String getValueString() {
+       return type.stringify(value);
+    }
 
     /**
      * Returns the type field this match field object is for
@@ -74,6 +89,11 @@ public class MatchField implements Cloneable {
         return type;
     }
 
+    @XmlElement(name="type")
+    private String getTypeString() {
+       return type.toString();
+    }
+
     /**
      * Returns the mask value set for this field match
      * A null mask means this is a full match
@@ -82,6 +102,11 @@ public class MatchField implements Cloneable {
     public Object getMask() {
         return mask;
     }
+    
+    @XmlElement(name="mask")
+    private String getMaskString() {
+       return type.stringify(mask); 
+    }
 
     /**
      * Returns the bitmask set for this field match
@@ -174,13 +199,6 @@ public class MatchField implements Cloneable {
 
     @Override
     public String toString() {
-        String valueString = (value == null) ? "null"
-                : (value instanceof byte[]) ? HexEncode
-                        .bytesToHexString((byte[]) value) : value.toString();
-        String maskString = (mask == null) ? "null"
-                : (mask instanceof byte[]) ? HexEncode
-                        .bytesToHexString((byte[]) mask) : mask.toString();
-
-        return type + "(" + valueString + "," + maskString + ")";
+        return type + "(" + getValueString() + "," + getMaskString() + ")";
     }
 }
index b54dd3bcd245bce7e923d77aa5dcd6fdf9e85d19..8379f073dc8f34e3f3744fd6072ca0969bbdf3fa 100644 (file)
@@ -12,6 +12,7 @@ package org.opendaylight.controller.sal.match;
 import java.net.InetAddress;
 
 import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.HexEncode;
 
 /**
  * Represents the binding between the id, the value and mask type and the range values
@@ -21,19 +22,19 @@ import org.opendaylight.controller.sal.core.NodeConnector;
  *
  */
 public enum MatchType {
-    IN_PORT("inPort", 1 << 0, NodeConnector.class, 1, 0), DL_SRC("dlSrc",
-            1 << 1, Byte[].class, 0, 0xffffffffffffL), DL_DST("dlDst", 1 << 2,
-            Byte[].class, 0, 0xffffffffffffL), DL_VLAN("dlVlan", 1 << 3,
-            Short.class, 2, 0xfff), // 2 bytes
+    IN_PORT("inPort", 1 << 0, NodeConnector.class, 1, 0), 
+    DL_SRC("dlSrc", 1 << 1, Byte[].class, 0, 0xffffffffffffL), 
+    DL_DST("dlDst", 1 << 2, Byte[].class, 0, 0xffffffffffffL), 
+    DL_VLAN("dlVlan", 1 << 3, Short.class, 2, 0xfff), // 2 bytes
     DL_VLAN_PR("dlVlanPriority", 1 << 4, Byte.class, 0, 0x7), // 3 bits
-    DL_OUTER_VLAN("dlOuterVlan", 1 << 5, Short.class, 2, 0xfff), DL_OUTER_VLAN_PR(
-            "dlOuterVlanPriority", 1 << 6, Short.class, 0, 0x7), DL_TYPE(
-            "dlType", 1 << 7, Short.class, 0, 0xffff), // 2 bytes
+    DL_OUTER_VLAN("dlOuterVlan", 1 << 5, Short.class, 2, 0xfff), 
+    DL_OUTER_VLAN_PR("dlOuterVlanPriority", 1 << 6, Short.class, 0, 0x7), 
+    DL_TYPE("dlType", 1 << 7, Short.class, 0, 0xffff), // 2 bytes
     NW_TOS("nwTOS", 1 << 8, Byte.class, 0, 0xff), // 1 byte
     NW_PROTO("nwProto", 1 << 9, Byte.class, 0, 0xff), // 1 byte
-    NW_SRC("nwSrc", 1 << 10, InetAddress.class, 0, 0), NW_DST("nwDst", 1 << 11,
-            InetAddress.class, 0, 0), TP_SRC("tpSrc", 1 << 12, Short.class, 1,
-            0xffff), // 2 bytes
+    NW_SRC("nwSrc", 1 << 10, InetAddress.class, 0, 0), 
+    NW_DST("nwDst", 1 << 11, InetAddress.class, 0, 0), 
+    TP_SRC("tpSrc", 1 << 12, Short.class, 1, 0xffff), // 2 bytes
     TP_DST("tpDst", 1 << 13, Short.class, 1, 0xffff); // 2 bytes
 
     private String id;
@@ -190,4 +191,32 @@ public enum MatchType {
         }
         return 0L;
     }
+
+       public String stringify(Object value) {
+               if (value == null) {
+                       return null;
+               }
+               
+               switch (this) {
+               case DL_DST:
+               case DL_SRC:
+                       return HexEncode.bytesToHexStringFormat((byte[])value);
+               case DL_TYPE:
+               case DL_VLAN:
+                       if ((Short)value < 0) {
+                               return ((Integer) (((Short)value).intValue() & 0x7FFF | 0x8000)).toString();
+                       }
+                       break;
+               case NW_SRC:
+               case NW_DST:
+                       return ((InetAddress)value).getHostAddress();
+               case TP_SRC:
+               case TP_DST:
+                       if ((Short)value < 0) {
+                               return ((Integer) (((Short)value).intValue() & 0x7FFF | 0x8000)).toString();
+                       }
+                       break;
+               }
+               return value.toString();
+       }
 }
index a89816ffafe0cf7aa7e8c591475042a48dd00ca7..b530e380229b54b09c34c177e791179fadb88ba8 100644 (file)
@@ -236,7 +236,7 @@ public class LLDPTLV extends Packet {
     static public String getHexStringValue(byte[] tlvValue, int tlvLen) {
        byte[] cidBytes = new byte[tlvLen - chassisIDSubType.length];                
         System.arraycopy(tlvValue, chassisIDSubType.length, cidBytes, 0, cidBytes.length);
-       return HexEncode.bytesToHexStringWithColumn(cidBytes);
+       return HexEncode.bytesToHexStringFormat(cidBytes);
     }
 
     /**
index 57dea9c3d9b2f8a103322a0766d436921d7089aa..63daf2076311bce850b10f699cbd50ec8ade7a3a 100644 (file)
@@ -13,6 +13,7 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
 
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.EqualsBuilder;
@@ -24,6 +25,7 @@ import org.opendaylight.controller.sal.utils.HexEncode;
 @XmlAccessorType(XmlAccessType.NONE)
 public class EthernetAddress extends DataLinkAddress {
     private static final long serialVersionUID = 1L;
+    @XmlTransient
     private byte[] macAddress;
 
     public static final EthernetAddress BROADCASTMAC = createWellKnownAddress(new byte[] {
@@ -109,6 +111,6 @@ public class EthernetAddress extends DataLinkAddress {
 
     @XmlElement(name = "macAddress")
     public String getMacAddress() {
-        return HexEncode.bytesToHexString(macAddress);
+        return HexEncode.bytesToHexStringFormat(macAddress);
     }
 }
index 1cc2caca9248eb1d4aeb0dee7bfa4b4550c570b5..557b848b73aa8e1327f95cc9f6c0c21f77bb6d3a 100644 (file)
@@ -74,7 +74,7 @@ public class HexEncode {
        /**
         * This method converts byte array into HexString format with ":" inserted.
         */
-    public static String bytesToHexStringWithColumn(byte[] bytes) {
+    public static String bytesToHexStringFormat(byte[] bytes) {
         int i;
         String ret = "";
         String tmp;
diff --git a/opendaylight/samples/simpleforwarding/pom.xml b/opendaylight/samples/simpleforwarding/pom.xml
new file mode 100644 (file)
index 0000000..7201b92
--- /dev/null
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>samples.simpleforwarding</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+                         org.opendaylight.controller.sal.utils,
+                         org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.forwardingrulesmanager,
+              org.opendaylight.controller.hosttracker,
+              org.opendaylight.controller.hosttracker.hostAware,
+              org.opendaylight.controller.switchmanager,
+              org.opendaylight.controller.clustering.services,
+              org.opendaylight.controller.sal.action,
+              org.opendaylight.controller.sal.flowprogrammer,
+              org.opendaylight.controller.sal.match,
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.sal.routing,
+              org.opendaylight.controller.topologymanager,
+              org.apache.commons.lang3.builder,
+                 org.junit;resolution:=optional,
+                 org.slf4j,
+              org.apache.felix.dm
+            </Import-Package>
+           <Bundle-Activator>
+                 org.opendaylight.controller.samples.simpleforwarding.internal.Activator
+                       </Bundle-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>topologymanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>forwardingrulesmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>hosttracker</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+       </dependency>    
+  </dependencies>
+</project>
diff --git a/opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/Activator.java b/opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/Activator.java
new file mode 100644 (file)
index 0000000..bf6c1f4
--- /dev/null
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.samples.simpleforwarding.internal;
+
+import org.apache.felix.dm.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.IfNewHostNotify;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.routing.IListenRoutingUpdates;
+import org.opendaylight.controller.sal.routing.IRouting;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { SimpleForwardingImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(SimpleForwardingImpl.class)) {
+            // export the service
+            c.setInterface(new String[] { IInventoryListener.class.getName(),
+                    IfNewHostNotify.class.getName(),
+                    IListenRoutingUpdates.class.getName() }, null);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IClusterContainerServices.class).setCallbacks(
+                    "setClusterContainerService",
+                    "unsetClusterContainerService").setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManager.class).setCallbacks("setSwitchManager",
+                    "unsetSwitchManager").setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IfIptoHost.class).setCallbacks("setHostTracker",
+                    "unsetHostTracker").setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IForwardingRulesManager.class).setCallbacks(
+                    "setForwardingRulesManager", "unsetForwardingRulesManager")
+                    .setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ITopologyManager.class).setCallbacks("setTopologyManager",
+                    "unsetTopologyManager").setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IRouting.class).setCallbacks("setRouting", "unsetRouting")
+                    .setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/HostNodePair.java b/opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/HostNodePair.java
new file mode 100644 (file)
index 0000000..2437b98
--- /dev/null
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.samples.simpleforwarding.internal;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.core.Node;
+
+/**
+ * Class that represent a pair of {Host, Node}, the intent of it
+ * is to be used as a key in the database kept by IPSwitching module
+ * where for every Host, Switch we will have a Forwarding Rule that
+ * will route the traffic toward the /32 destination
+ *
+ */
+public class HostNodePair implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private HostNodeConnector host;
+    private Node node;
+
+    public HostNodePair(HostNodeConnector h, Node s) {
+        setNode(s);
+        setHost(h);
+    }
+
+    public Node getNode() {
+        return node;
+    }
+
+    public void setNode(Node nodeId) {
+        this.node = nodeId;
+    }
+
+    public HostNodeConnector getHost() {
+        return host;
+    }
+
+    public void setHost(HostNodeConnector host) {
+        this.host = host;
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public String toString() {
+        return "HostNodePair[" + ReflectionToStringBuilder.toString(this) + "]";
+    }
+}
diff --git a/opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/SimpleForwardingImpl.java b/opendaylight/samples/simpleforwarding/src/main/java/org/opendaylight/controller/samples/simpleforwarding/internal/SimpleForwardingImpl.java
new file mode 100644 (file)
index 0000000..9e838d2
--- /dev/null
@@ -0,0 +1,942 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.samples.simpleforwarding.internal;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.hosttracker.IfIptoHost;
+import org.opendaylight.controller.hosttracker.IfNewHostNotify;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.PopVlan;
+import org.opendaylight.controller.sal.action.SetDlDst;
+import org.opendaylight.controller.sal.action.SetVlanId;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+import org.opendaylight.controller.sal.core.Path;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.routing.IListenRoutingUpdates;
+import org.opendaylight.controller.sal.routing.IRouting;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SimpleForwardingImpl implements IfNewHostNotify,
+        IListenRoutingUpdates, IInventoryListener {
+    private static Logger log = LoggerFactory
+            .getLogger(SimpleForwardingImpl.class);
+    private static short DEFAULT_IPSWITCH_PRIORITY = 1;
+    private IfIptoHost hostTracker;
+    private IForwardingRulesManager frm;
+    private ITopologyManager topologyManager;
+    private IRouting routing;
+    private ConcurrentMap<HostNodePair, HashMap<NodeConnector, FlowEntry>> rulesDB;
+    private Map<Node, List<FlowEntry>> tobePrunedPos = new HashMap<Node, List<FlowEntry>>();
+    private IClusterContainerServices clusterContainerService = null;
+    private ISwitchManager switchManager;
+
+    /**
+     * Return codes from the programming of the perHost rules in HW
+     *
+     */
+    public enum RulesProgrammingReturnCode {
+        SUCCESS, FAILED_FEW_SWITCHES, FAILED_ALL_SWITCHES, FAILED_WRONG_PARAMS
+    }
+
+    public void setRouting(IRouting routing) {
+        this.routing = routing;
+    }
+
+    public void unsetRouting(IRouting routing) {
+        if (this.routing == routing) {
+            this.routing = null;
+        }
+    }
+
+    public ITopologyManager getTopologyManager() {
+        return topologyManager;
+    }
+
+    public void setTopologyManager(ITopologyManager topologyManager) {
+        log.debug("Setting topologyManager");
+        this.topologyManager = topologyManager;
+    }
+
+    public void unsetTopologyManager(ITopologyManager topologyManager) {
+        if (this.topologyManager == topologyManager) {
+            this.topologyManager = null;
+        }
+    }
+
+    public void setHostTracker(IfIptoHost hostTracker) {
+        log.debug("Setting HostTracker");
+        this.hostTracker = hostTracker;
+    }
+
+    public void setForwardingRulesManager(
+            IForwardingRulesManager forwardingRulesManager) {
+        log.debug("Setting ForwardingRulesManager");
+        this.frm = forwardingRulesManager;
+    }
+
+    public void unsetHostTracker(IfIptoHost hostTracker) {
+        if (this.hostTracker == hostTracker) {
+            this.hostTracker = null;
+        }
+    }
+
+    public void unsetForwardingRulesManager(
+            IForwardingRulesManager forwardingRulesManager) {
+        if (this.frm == forwardingRulesManager) {
+            this.frm = null;
+        }
+    }
+
+    /**
+     * Function called when the bundle gets activated
+     *
+     */
+    public void startUp() {
+        allocateCaches();
+        retrieveCaches();
+    }
+
+    /**
+     * Function called when the bundle gets stopped
+     *
+     */
+    public void shutDown() {
+        log.debug("Destroy all the host Rules given we are shutting down");
+        uninstallPerHostRules();
+        destroyCaches();
+    }
+
+    @SuppressWarnings("deprecation")
+       private void allocateCaches() {
+        if (this.clusterContainerService == null) {
+            log.info("un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+
+        try {
+            clusterContainerService.createCache("forwarding.ipswitch.rules",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheExistException cee) {
+            log.error("\nCache already exists - destroy and recreate if needed");
+        } catch (CacheConfigException cce) {
+            log.error("\nCache configuration invalid - check cache mode");
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    private void retrieveCaches() {
+        if (this.clusterContainerService == null) {
+            log.info("un-initialized clusterContainerService, can't retrieve cache");
+            return;
+        }
+
+        rulesDB = (ConcurrentMap<HostNodePair, HashMap<NodeConnector, FlowEntry>>) clusterContainerService
+                .getCache("forwarding.ipswitch.rules");
+        if (rulesDB == null) {
+            log.error("\nFailed to get rulesDB handle");
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+       private void destroyCaches() {
+        if (this.clusterContainerService == null) {
+            log.info("un-initialized clusterContainerService, can't destroy cache");
+            return;
+        }
+
+        clusterContainerService.destroyCache("forwarding.ipswitch.rules");
+    }
+
+    @SuppressWarnings("unused")
+    private void updatePerHostRuleInSW(HostNodeConnector host, Node currNode,
+            Node rootNode, Edge link, HostNodePair key,
+            Set<NodeConnector> passedPorts) {
+
+        // link parameter it's optional
+        if (host == null || key == null || currNode == null || rootNode == null) {
+            return;
+        }
+        Set<NodeConnector> ports = passedPorts;
+        // TODO: Replace this with SAL equivalent when available
+        //if (container == null) {
+        ports = new HashSet<NodeConnector>();
+        ports.add(NodeConnectorCreator.createNodeConnector(
+                NodeConnectorIDType.ALL, NodeConnector.SPECIALNODECONNECTORID,
+                currNode));
+        //}
+
+        HashMap<NodeConnector, FlowEntry> pos = this.rulesDB.get(key);
+        if (pos == null) {
+            pos = new HashMap<NodeConnector, FlowEntry>();
+        }
+        if (ports == null) {
+            log.debug("Empty port list, nothing to do");
+            return;
+        }
+        for (NodeConnector inPort : ports) {
+            /*
+             * skip the port connected to the target host
+             */
+            if (currNode.equals(rootNode)
+                    && (host.getnodeConnector().equals(inPort))) {
+                continue;
+            }
+            FlowEntry removed_po = pos.remove(inPort);
+            Match match = new Match();
+            List<Action> actions = new ArrayList<Action>();
+            // IP destination based forwarding
+            //on /32 entries only!
+            match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
+            match.setField(MatchType.NW_DST, host.getNetworkAddress());
+
+            //Action for the policy if to
+            //forward to a port except on the
+            //switch where the host sits,
+            //which is to rewrite also the MAC
+            //and to forward on the Host port
+            NodeConnector outPort = null;
+
+            if (currNode.equals(rootNode)) {
+                outPort = host.getnodeConnector();
+                if (inPort.equals(outPort)) {
+                    /*
+                     * skip the host port
+                     */
+                    continue;
+                }
+                actions.add(new SetDlDst(host.getDataLayerAddressBytes()));
+
+                if (!inPort.getType().equals(
+                        NodeConnectorIDType.ALL)) {
+                    /*
+                     * Container mode: at the destination switch, we need to strip out the tag (VLAN)
+                     */
+                    actions.add(new PopVlan());
+                }
+            } else {
+                /*
+                 * currNode is NOT the rootNode
+                 */
+                if (link != null) {
+                    outPort = link.getTailNodeConnector();
+                    if (inPort.equals(outPort)) {
+                        /*
+                         * skip the outgoing port
+                         */
+                        continue;
+                    }
+                    /*
+                     *  If outPort is network link, add VLAN tag
+                     */
+                    if (topologyManager.isInternal(outPort)) {
+                        log.debug("outPort {}/{} is internal uplink port",
+                                currNode, outPort);
+                    } else {
+                        log.debug("outPort {}/{} is host facing port",
+                                currNode, outPort);
+                    }
+
+                    if ((!inPort.getType().equals(
+                            NodeConnectorIDType.ALL))
+                            && (topologyManager.isInternal(outPort))) {
+                        Node nextNode = link.getHeadNodeConnector()
+                                .getNode();
+                        // TODO: Replace this with SAL equivalent
+                        //short tag = container.getTag((Long)nextNode.getNodeID());
+                        short tag = 0;
+                        if (tag != 0) {
+                            log.debug("adding SET_VLAN " + tag
+                                    + "  for traffic leaving " + currNode + "/"
+                                    + outPort + "toward switch " + nextNode);
+                            actions.add(new SetVlanId(tag));
+                        } else {
+                            log.debug("No tag assigned to switch " + nextNode);
+                        }
+                    }
+                }
+            }
+            if (outPort != null) {
+                actions.add(new Output(outPort));
+            }
+            if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
+                /*
+                 * include input port in the flow match field
+                 */
+                match.setField(MatchType.IN_PORT, inPort);
+
+                if (topologyManager.isInternal(inPort)) {
+                    log.debug("inPort {}/{} is internal uplink port", currNode,
+                            inPort);
+                } else {
+                    log.debug("inPort {}/{} is host facing port", currNode,
+                            inPort);
+                }
+                /*
+                 * for incoming network link; if the VLAN tag is defined, include it for incoming flow matching
+                 */
+                if (topologyManager.isInternal(inPort)) {
+                    // TODO: Replace this with SAL equivalent
+                    //short tag = container.getTag((Long)currNode.getNodeID());
+                    short tag = 0;
+                    if (tag != 0) {
+                        log.debug("adding MATCH VLAN " + tag
+                                + "  for traffic entering " + currNode + "/"
+                                + inPort);
+                        match.setField(MatchType.DL_VLAN, tag);
+                    } else {
+                        log.debug("No tag assigned to switch " + currNode);
+                    }
+                }
+            }
+            // Make sure the priority for IP switch entries is
+            // set to a level just above default drop entries
+            Flow flow = new Flow(match, actions);
+            flow.setIdleTimeout((short) 0);
+            flow.setHardTimeout((short) 0);
+            flow.setPriority(DEFAULT_IPSWITCH_PRIORITY);
+
+            String policyName = host.getNetworkAddress().getHostAddress()
+                    + "/32";
+            String flowName = "["
+                    + (!inPort.getType().equals(NodeConnectorIDType.ALL) ?
+                       (inPort.getID()).toString()
+                       + "," : "")
+                    + host.getNetworkAddress().getHostAddress() + "/32 on N "
+                    + currNode + "]";
+            FlowEntry po = new FlowEntry(policyName, flowName, flow, currNode);
+
+            // Now save the rule in the DB rule,
+            // so on updates from topology we can
+            // selectively
+            pos.put(inPort, po);
+            this.rulesDB.put(key, pos);
+            if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
+                log.debug("Adding Match(inPort=" + inPort + ",DIP="
+                        + host.getNetworkAddress().getHostAddress()
+                        + ") Action(outPort=" + outPort + ") to node "
+                        + currNode);
+                if ((removed_po != null)
+                        && (!po.getFlow().getMatch().equals(
+                                removed_po.getFlow().getMatch()))) {
+                    log.debug("Old Flow match: {}, New Flow match: {}",
+                            removed_po.getFlow().getMatch(), po.getFlow()
+                                    .getMatch());
+                    addTobePrunedPolicy(currNode, removed_po, po);
+                }
+
+            } else {
+                log.debug("Adding policy Match(DIP="
+                        + host.getNetworkAddress().getHostAddress()
+                        + ") Action(outPort=" + outPort + ") to node "
+                        + currNode);
+            }
+        }
+    }
+
+    /**
+     * Calculate the per-Host rules to be installed in the rulesDB,
+     * and that will later on be installed in HW, this routine will
+     * implicitly calculate the shortest path tree among the switch
+     * to which the host is attached and all the other switches in the
+     * network and will automatically create all the rules that allow
+     * a /32 destination IP based forwarding, as in traditional IP
+     * networks.
+     *
+     * @param host Host for which we are going to prepare the rules in the rulesDB
+     *
+     * @return A set of switches touched by the calculation
+     */
+    private Set<Node> preparePerHostRules(HostNodeConnector host) {
+        if (host == null) {
+            return null;
+        }
+        if (this.routing == null) {
+            return null;
+        }
+        if (this.switchManager == null) {
+            return null;
+        }
+        if (this.rulesDB == null) {
+            return null;
+        }
+
+        Node rootNode = host.getnodeconnectorNode();
+        Set<Node> nodes = this.switchManager.getNodes();
+        Set<Node> switchesToProgram = new HashSet<Node>();
+        HostNodePair key;
+        HashMap<NodeConnector, FlowEntry> pos;
+        FlowEntry po;
+
+        for (Node node : nodes) {
+            if (node.equals(rootNode)) {
+                // We skip it because for the node with host attached
+                // we will process in every case even if there are no
+                // routes
+                continue;
+            }
+            List<Edge> links;
+            Path res = this.routing.getRoute(node, rootNode);
+            if ((res == null) || ((links = res.getEdges()) == null)) {
+                // Still the path that connect node to rootNode
+                // doesn't exists
+                log.debug("NO Route/Path between SW[" + node + "] --> SW["
+                        + rootNode + "] cleaning potentially existing entries");
+                key = new HostNodePair(host, node);
+                pos = this.rulesDB.get(key);
+                if (pos != null) {
+                    for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
+                        po = e.getValue();
+                        if (po != null) {
+                            //Uninstall the policy
+                            this.frm.uninstallFlowEntry(po);
+                        }
+                    }
+                    this.rulesDB.remove(key);
+                }
+                continue;
+            }
+
+            log.debug("Route between SW[" + node + "] --> SW[" + rootNode
+                            + "]");
+            Integer curr;
+            Node currNode = node;
+            key = new HostNodePair(host, currNode);
+            Edge link;
+            for (curr = 0; curr < links.size(); curr++) {
+                link = links.get(curr);
+                if (link == null) {
+                    log.error("Could not retrieve the Link");
+                    continue;
+                }
+
+                log.debug(link.toString());
+
+                // Index all the switches to be programmed
+                // switchesToProgram.add(currNode);
+                Set<NodeConnector> ports = null;
+                ports = switchManager.getUpNodeConnectors(currNode);
+                updatePerHostRuleInSW(host, currNode, rootNode, link, key,
+                        ports);
+                if ((this.rulesDB.get(key)) != null) {
+                    /*
+                     * Calling updatePerHostRuleInSW() doesn't guarantee that rules will be
+                     * added in currNode (e.g, there is only one link from currNode to rootNode
+                     * This check makes sure that there are some rules in the rulesDB for the
+                     * given key prior to adding switch to switchesToProgram
+                     */
+                    switchesToProgram.add(currNode);
+                }
+                currNode = link.getHeadNodeConnector().getNode();
+                key = new HostNodePair(host, currNode);
+            }
+        }
+
+        // This rule will be added no matter if any topology is built
+        // or no, it serve as a way to handle the case of a node with
+        // multiple hosts attached to it but not yet connected to the
+        // rest of the world
+        switchesToProgram.add(rootNode);
+        Set<NodeConnector> ports = switchManager
+                .getUpNodeConnectors(rootNode);
+        updatePerHostRuleInSW(host, rootNode, rootNode, null, new HostNodePair(
+                host, rootNode), ports);
+
+        //             log.debug("Getting out at the end!");
+        return switchesToProgram;
+    }
+
+    /**
+     * Calculate the per-Host rules to be installed in the rulesDB
+     * from  a specific switch when a host facing port comes up.
+     * These rules will later on be installed in HW. This routine
+     * will implicitly calculate the shortest path from the switch
+     * where the port has come up to the switch where host is ,
+     * attached and will automatically create all the rules that allow
+     * a /32 destination IP based forwarding, as in traditional IP
+     * networks.
+     *
+     * @param host Host for which we are going to prepare the rules in the rulesDB
+     * @param swId Switch ID where the port has come up
+     *
+     * @return A set of switches touched by the calculation
+     */
+    private Set<Node> preparePerHostPerSwitchRules(HostNodeConnector host,
+            Node node, NodeConnector swport) {
+        if ((host == null) || (node == null)) {
+            return null;
+        }
+        if (this.routing == null) {
+            return null;
+        }
+        if (this.switchManager == null) {
+            return null;
+        }
+        if (this.rulesDB == null) {
+            return null;
+        }
+
+        Node rootNode = host.getnodeconnectorNode();
+        Set<Node> switchesToProgram = new HashSet<Node>();
+        HostNodePair key;
+        Map<NodeConnector, FlowEntry> pos;
+        FlowEntry po;
+        Set<NodeConnector> ports = new HashSet<NodeConnector>();
+        ports.add(swport);
+        List<Edge> links;
+
+        Path res = this.routing.getRoute(node, rootNode);
+        if ((res == null) || ((links = res.getEdges()) == null)) {
+            // Still the path that connect node to rootNode
+            // doesn't exists
+            log.debug("NO Route/Path between SW[" + node + "] --> SW["
+                    + rootNode + "] cleaning potentially existing entries");
+            key = new HostNodePair(host, node);
+            pos = this.rulesDB.get(key);
+            if (pos != null) {
+                for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
+                    po = e.getValue();
+                    if (po != null) {
+                        //Uninstall the policy
+                        this.frm.uninstallFlowEntry(po);
+                    }
+                }
+                this.rulesDB.remove(key);
+            }
+            return null;
+        }
+
+        log.debug("Route between SW[" + node + "] --> SW[" + rootNode + "]");
+        Integer curr;
+        Node currNode = node;
+        key = new HostNodePair(host, currNode);
+        Edge link;
+        for (curr = 0; curr < links.size(); curr++) {
+            link = links.get(curr);
+            if (link == null) {
+                log.error("Could not retrieve the Link");
+                continue;
+            }
+
+            log.debug("Link [" + currNode + "/" + link.getHeadNodeConnector()
+                    + "] --> ["
+                    + link.getHeadNodeConnector().getNode() + "/"
+                    + link.getTailNodeConnector() + "]");
+
+            // Index all the switches to be programmed
+            switchesToProgram.add(currNode);
+            updatePerHostRuleInSW(host, currNode, rootNode, link, key, ports);
+            break; // come out of the loop for port up case, interested only in programming one switch
+        }
+
+        // This rule will be added no matter if any topology is built
+        // or no, it serve as a way to handle the case of a node with
+        // multiple hosts attached to it but not yet connected to the
+        // rest of the world
+        // switchesToProgram.add(rootNode);
+        //updatePerHostRuleInSW(host, rootNode,
+        //                                       rootNode, null,
+        //                                       new HostNodePair(host, rootNode),ports);
+
+        //             log.debug("Getting out at the end!");
+        return switchesToProgram;
+    }
+
+    /**
+     * Routine that fetch the per-Host rules from the rulesDB and
+     * install in HW, the one having the same match rules will be
+     * overwritten silently.
+     *
+     * @param host host for which we want to install in HW the per-Host rules
+     * @param switchesToProgram list of switches to be programmed in
+     * HW, usually are them all, but better to be explicit, that list
+     * may change with time based on new switch addition/removal
+     *
+     * @return a return code that convey the programming status of the HW
+     */
+    private RulesProgrammingReturnCode installPerHostRules(
+            HostNodeConnector host, Set<Node> switchesToProgram) {
+        RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
+        if (host == null || switchesToProgram == null) {
+            return RulesProgrammingReturnCode.FAILED_WRONG_PARAMS;
+        }
+        Map<NodeConnector, FlowEntry> pos;
+        FlowEntry po;
+        // Now program every single switch
+        log.debug("Inside installPerHostRules");
+        for (Node swId : switchesToProgram) {
+            HostNodePair key = new HostNodePair(host, swId);
+            pos = this.rulesDB.get(key);
+            if (pos == null) {
+                continue;
+            }
+            for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
+                po = e.getValue();
+                if (po != null) {
+                    // Populate the Policy field now
+                    Status poStatus = this.frm.installFlowEntry(po);
+                    if (!poStatus.isSuccess()) {
+                        log.error("Failed to install policy: "
+                                + po.getGroupName() + " (" 
+                                + poStatus.getDescription() + ")");
+
+                        retCode = RulesProgrammingReturnCode.FAILED_FEW_SWITCHES;
+                        // Remove the entry from the DB, it was not installed!
+                        this.rulesDB.remove(key);
+                    } else {
+                        log.debug("Successfully installed policy "
+                                + po.toString() + " on switch " + swId);
+                    }
+                } else {
+                    log.error("Cannot find a policy for SW:{" + swId
+                            + "} Host: {" + host + "}");
+                    /* // Now dump every single rule */
+                    /* for (HostNodePair dumpkey : this.rulesDB.keySet()) { */
+                    /*         po = this.rulesDB.get(dumpkey); */
+                    /*         log.debug("Dumping entry H{" + dumpkey.getHost() + "} S{" + dumpkey.getSwitchId() + "} = {" + (po == null ? "null policy" : po)); */
+                    /* } */
+                }
+            }
+        }
+        log.debug("Leaving installPerHostRules");
+        return retCode;
+    }
+
+    /**
+     * Cleanup all the host rules for a given host
+     *
+     * @param host Host for which the host rules need to be cleaned
+     * up, the host could be null in that case it match all the hosts
+     *
+     * @return a return code that convey the programming status of the HW
+     */
+    private RulesProgrammingReturnCode uninstallPerHostRules(
+            HostNodeConnector host) {
+        RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
+        Map<NodeConnector, FlowEntry> pos;
+        FlowEntry po;
+        // Now program every single switch
+        for (HostNodePair key : this.rulesDB.keySet()) {
+            if (host == null || key.getHost().equals(host)) {
+                pos = this.rulesDB.get(key);
+                for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
+                    po = e.getValue();
+                    if (po != null) {
+                        // Uninstall the policy
+                        this.frm.uninstallFlowEntry(po);
+                    }
+                }
+                this.rulesDB.remove(key);
+            }
+        }
+        return retCode;
+    }
+
+    /**
+     * Cleanup all the host rules for a given node, triggered when the
+     * switch disconnects, so there is no reason for Hw cleanup
+     * because it's disconnected anyhow
+     * TBD - Revisit above stmt in light of CSCus88743
+     * @param targetNode Node for which we want to do cleanup
+     *
+     */
+    private void uninstallPerNodeRules(Node targetNode) {
+        //RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
+        Map<NodeConnector, FlowEntry> pos;
+        FlowEntry po;
+
+        // Now program every single switch
+        for (HostNodePair key : this.rulesDB.keySet()) {
+            Node node = key.getNode();
+            if (targetNode == null || node.equals(targetNode)) {
+                log.debug("Work on " + node + " host " + key.getHost());
+                pos = this.rulesDB.get(key);
+                for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
+                    po = e.getValue();
+                    if (po != null) {
+                        // Uninstall the policy
+                        this.frm.uninstallFlowEntry(po);
+                    }
+                }
+                log.debug("Remove " + key);
+                this.rulesDB.remove(key);
+            }
+        }
+    }
+
+    /**
+     * Cleanup all the host rules currently present in the rulesDB
+     *
+     * @return a return code that convey the programming status of the HW
+     */
+    private RulesProgrammingReturnCode uninstallPerHostRules() {
+        return uninstallPerHostRules(null);
+    }
+
+    @Override
+    public void recalculateDone() {
+        if (this.hostTracker == null) {
+            //Not yet ready to process all the updates
+            return;
+        }
+        Set<HostNodeConnector> allHosts = this.hostTracker.getAllHosts();
+        for (HostNodeConnector host : allHosts) {
+            Set<Node> switches = preparePerHostRules(host);
+            if (switches != null) {
+                // This will refresh existing rules, by overwriting
+                // the previous ones
+                installPerHostRules(host, switches);
+                pruneExcessRules(switches);
+            }
+        }
+    }
+
+    void addTobePrunedPolicy(Node swId, FlowEntry po, FlowEntry new_po) {
+        List<FlowEntry> pl = tobePrunedPos.get(swId);
+        if (pl == null) {
+            pl = new LinkedList<FlowEntry>();
+            tobePrunedPos.put(swId, pl);
+        }
+        pl.add(po);
+        log.debug("Adding Pruned Policy for SwId: {}", swId);
+        log.debug("Old Policy: " + po.toString());
+        log.debug("New Policy: " + new_po.toString());
+    }
+
+    private void pruneExcessRules(Set<Node> switches) {
+        for (Node swId : switches) {
+            List<FlowEntry> pl = tobePrunedPos.get(swId);
+            if (pl != null) {
+                log
+                        .debug(
+                                "Policies for Switch: {} in the list to be deleted: {}",
+                                swId, pl);
+                Iterator<FlowEntry> plIter = pl.iterator();
+                //for (Policy po: pl) {
+                while (plIter.hasNext()) {
+                    FlowEntry po = plIter.next();
+                    log.error("Removing Policy, Switch: {} Policy: {}", swId,
+                            po);
+                    this.frm.uninstallFlowEntry(po);
+                    plIter.remove();
+                }
+            }
+            // tobePrunedPos.remove(swId);
+        }
+    }
+
+    /*
+     * A Host facing port has come up in a container. Add rules on the switch where this
+     * port has come up for all the known hosts to the controller.
+     * @param swId switch id of the port where port came up
+     * @param swPort port which came up
+     */
+    private void updateRulesforHIFup(Node node, NodeConnector swPort) {
+        if (this.hostTracker == null) {
+            //Not yet ready to process all the updates
+            return;
+        }
+        log.debug("Host Facing Port in a container came up, install the rules for all hosts from this port !");
+        Set<HostNodeConnector> allHosts = this.hostTracker.getAllHosts();
+        for (HostNodeConnector host : allHosts) {
+            if (node.equals(host.getnodeconnectorNode())
+                    && swPort.equals(host.getnodeConnector())) {
+                /*
+                 * This host resides behind the same switch and port for which a port up
+                 * message is received. Ideally this should not happen, but if it does,
+                 * don't program any rules for this host
+                 */
+                continue;
+            }
+            Set<Node> switches = preparePerHostPerSwitchRules(host, node,
+                    swPort);
+            if (switches != null) {
+                // This will refresh existing rules, by overwriting
+                // the previous ones
+                installPerHostRules(host, switches);
+            }
+        }
+
+    }
+
+    @Override
+    public void notifyHTClient(HostNodeConnector host) {
+        if (host == null) {
+            return;
+        }
+        Set<Node> switches = preparePerHostRules(host);
+        if (switches != null) {
+            installPerHostRules(host, switches);
+        }
+    }
+
+    @Override
+    public void notifyHTClientHostRemoved(HostNodeConnector host) {
+        if (host == null) {
+            return;
+        }
+        uninstallPerHostRules(host);
+    }
+
+    @Override
+    public void notifyNode(Node node, UpdateType type,
+            Map<String, Property> propMap) {
+        if (node == null)
+            return;
+
+        switch (type) {
+        case REMOVED:
+            log.debug("Node " + node + " gone, doing a cleanup");
+            uninstallPerNodeRules(node);
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void notifyNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Map<String, Property> propMap) {
+        if (nodeConnector == null)
+            return;
+
+        boolean up = false;
+        switch (type) {
+        case ADDED:
+            up = true;
+            break;
+        case REMOVED:
+            break;
+        case CHANGED:
+            State state = (State) propMap.get(State.StatePropName);
+            if ((state != null) && (state.getValue() == State.EDGE_UP)) {
+                up = true;
+            }
+            break;
+        default:
+            return;
+        }
+
+        if (up) {
+            handleNodeConnectorStatusUp(nodeConnector);
+        } else {
+            handleNodeConnectorStatusDown(nodeConnector);
+        }
+    }
+
+    private void handleNodeConnectorStatusUp(NodeConnector nodeConnector) {
+        if (topologyManager == null) {
+            log.debug("topologyManager is not set yet");
+            return;
+        }
+
+        if (topologyManager.isInternal(nodeConnector)) {
+            log.debug("{} is not a host facing link", nodeConnector);
+            return;
+        }
+
+        log.debug("{} is up", nodeConnector);
+        updateRulesforHIFup(nodeConnector.getNode(), nodeConnector);
+    }
+
+    private void handleNodeConnectorStatusDown(NodeConnector nodeConnector) {
+        log.debug("{} is down", nodeConnector);
+    }
+
+    void setClusterContainerService(IClusterContainerServices s) {
+        log.debug("Cluster Service set");
+        this.clusterContainerService = s;
+    }
+
+    void unsetClusterContainerService(IClusterContainerServices s) {
+        if (this.clusterContainerService == s) {
+            log.debug("Cluster Service removed!");
+            this.clusterContainerService = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        startUp();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    public void setSwitchManager(ISwitchManager switchManager) {
+        this.switchManager = switchManager;
+    }
+
+    public void unsetSwitchManager(ISwitchManager switchManager) {
+        if (this.switchManager == switchManager) {
+            this.switchManager = null;
+        }
+    }
+}
diff --git a/opendaylight/samples/simpleforwarding/src/test/java/org/opendaylight/controller/samples/simpleforwarding/internal/HostSwitchTest.java b/opendaylight/samples/simpleforwarding/src/test/java/org/opendaylight/controller/samples/simpleforwarding/internal/HostSwitchTest.java
new file mode 100644 (file)
index 0000000..afa4c42
--- /dev/null
@@ -0,0 +1,379 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.samples.simpleforwarding.internal;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import org.junit.Test;
+import org.junit.Assert;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.samples.simpleforwarding.internal.HostNodePair;
+
+public class HostSwitchTest {
+    @Test
+    public void TestEquality() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        Assert.assertTrue(hsw1.equals(hsw2));
+    }
+
+    @Test
+    public void TestDiversityHost() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.2");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        Assert.assertTrue(!hsw1.equals(hsw2));
+    }
+
+    @Test
+    public void TestDiversitySwitch() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(2l));
+        Assert.assertTrue(!hsw1.equals(hsw2));
+    }
+
+    @Test
+    public void TestDiversityAll() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.2");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(2l));
+        Assert.assertTrue(!hsw1.equals(hsw2));
+    }
+
+    @Test
+    public void TestEqualHashCode1() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        Assert.assertTrue(hsw1.hashCode() == hsw2.hashCode());
+    }
+
+    @Test
+    public void TestEqualHashCode2() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.2");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.2");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        Assert.assertTrue(hsw1.hashCode() == hsw2.hashCode());
+    }
+
+    @Test
+    public void TestDiverseHashCodeHost() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.2");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        Assert.assertTrue(hsw1.hashCode() != hsw2.hashCode());
+    }
+
+    @Test
+    public void TestDiverseHashCodeSwitch() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(2l));
+        Assert.assertTrue(hsw1.hashCode() != hsw2.hashCode());
+    }
+
+    @Test
+    public void TestDiverseHashCodeAll() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.3");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(2l));
+        Assert.assertTrue(hsw1.hashCode() != hsw2.hashCode());
+    }
+
+    @Test
+    public void TestUsageAsKey() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+
+        HostNodePair hsw1 = new HostNodePair(h1, NodeCreator.createOFNode(1l));
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        HashMap<HostNodePair, Long> hm = new HashMap<HostNodePair, Long>();
+        hm.put(hsw1, new Long(10));
+        Assert.assertTrue(hm.get(hsw2) != null);
+        Assert.assertTrue(hm.get(hsw2).equals(new Long(10)));
+    }
+
+    @Test
+    public void TestUsageAsKeyChangingField() {
+        HostNodeConnector h1 = null;
+        HostNodeConnector h2 = null;
+        InetAddress ip1 = null;
+        try {
+            ip1 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        InetAddress ip2 = null;
+        try {
+            ip2 = InetAddress.getByName("10.0.0.1");
+        } catch (UnknownHostException e) {
+            return;
+        }
+        try {
+            h1 = new HostNodeConnector(ip1);
+        } catch (ConstructionException e) {
+            return;
+        }
+        try {
+            h2 = new HostNodeConnector(ip2);
+        } catch (ConstructionException e) {
+            return;
+        }
+        HostNodePair hsw1 = new HostNodePair(h1, null);
+        HostNodePair hsw2 = new HostNodePair(h2, NodeCreator.createOFNode(1l));
+        hsw1.setNode(NodeCreator.createOFNode(1l));
+        HashMap<HostNodePair, Long> hm = new HashMap<HostNodePair, Long>();
+        hm.put(hsw1, new Long(10));
+        Assert.assertTrue(hm.get(hsw2) != null);
+        Assert.assertTrue(hm.get(hsw2).equals(new Long(10)));
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/statisticsmanager/pom.xml b/opendaylight/statisticsmanager/pom.xml
new file mode 100644 (file)
index 0000000..2b0b00e
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>statisticsmanager</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+               <groupId>org.apache.felix</groupId>
+               <artifactId>maven-bundle-plugin</artifactId>
+               <version>2.3.6</version>
+               <extensions>true</extensions>
+               <configuration>
+                 <instructions>
+                   <Import-Package>
+                       org.opendaylight.controller.forwardingrulesmanager,
+                               org.opendaylight.controller.containermanager,
+                               org.opendaylight.controller.sal.core,
+                               org.opendaylight.controller.sal.flowprogrammer,
+                               org.slf4j,
+                               org.opendaylight.controller.sal.reader,
+                               org.apache.felix.dm           
+               </Import-Package>
+                       <Bundle-Activator>
+                               org.opendaylight.controller.statisticsmanager.internal.Activator
+                       </Bundle-Activator>
+                       <Export-Package>
+                               org.opendaylight.controller.statisticsmanager
+               </Export-Package>
+                 </instructions>
+               </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>containermanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>forwardingrulesmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+       <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/IStatisticsManager.java b/opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/IStatisticsManager.java
new file mode 100644 (file)
index 0000000..db7489d
--- /dev/null
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statisticsmanager;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.reader.NodeDescription;
+
+/**
+ * Interface which defines the available methods for retrieving
+ * the network nodes statistics.
+ */
+public interface IStatisticsManager {
+    /**
+     * Return all the statistics for all the flows present on the specified node in the current container context.
+     * If the context is the default container, the returned statistics are for all the flows installed on the node,
+     * regardless of the container they belong to
+     *
+     * @param node     the network node
+     * @return the list of flows installed on the network node
+     */
+    List<FlowOnNode> getFlows(Node node);
+
+    /**
+     * Returns the statistics for the flows specified in the list
+     *
+     * @param flows
+     * @return the list of flows installed on the network node
+     */
+    Map<Node, List<FlowOnNode>> getFlowStatisticsForFlowList(
+            List<FlowEntry> flows);
+
+    /**
+     * Returns the number of flows installed on the switch in the current container context
+     * If the context is the default container, the returned value is the number of all the
+     * flows installed on the switch regardless of the container they belong to
+     *
+     * @param switchId
+     * @return
+     */
+    int getFlowsNumber(Node node);
+
+    /**
+     * Returns the node description for the specified node retrieved and cached by the
+     * protocol plugin component which collects the node statistics
+     *
+     * @param node
+     * @return
+     */
+    NodeDescription getNodeDescription(Node node);
+
+    /**
+     * Returns the statistics for the specified node connector as it was retrieved
+     * and cached by the protocol plugin component which collects the node connector statistics
+     *
+     * @param node
+     * @return
+     */
+    NodeConnectorStatistics getNodeConnectorStatistics(
+            NodeConnector nodeConnector);
+
+    /**
+     * Returns the statistics for all the node connector present on the specified network node
+     *
+     * @param node
+     * @return
+     */
+    List<NodeConnectorStatistics> getNodeConnectorStatistics(Node node);
+}
diff --git a/opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/StatisticsManager.java b/opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/StatisticsManager.java
new file mode 100644 (file)
index 0000000..6104c1b
--- /dev/null
@@ -0,0 +1,132 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statisticsmanager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.IReadService;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.reader.NodeDescription;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The class which implements the methods for retrieving
+ * the network nodes statistics.
+ */
+public class StatisticsManager implements IStatisticsManager {
+    private static final Logger log = LoggerFactory
+            .getLogger(StatisticsManager.class);
+    private IReadService reader;
+
+    public StatisticsManager() {
+
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+        log.debug("INIT called!");
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        log.debug("DESTROY called!");
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+        log.debug("START called!");
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+        log.debug("STOP called!");
+    }
+
+    public void setReaderService(IReadService service) {
+        log.debug("Got inventory service set request {}", service);
+        this.reader = service;
+    }
+
+    public void unsetReaderService(IReadService service) {
+        log.debug("Got a service UNset request");
+        this.reader = null;
+    }
+
+    @Override
+    public List<FlowOnNode> getFlows(Node node) {
+        return reader.readAllFlows(node);
+    }
+
+    @Override
+    public Map<Node, List<FlowOnNode>> getFlowStatisticsForFlowList(
+            List<FlowEntry> flowList) {
+        Map<Node, List<FlowOnNode>> map = new HashMap<Node, List<FlowOnNode>>();
+        if (flowList != null) {
+            for (FlowEntry entry : flowList) {
+                Node node = entry.getNode();
+                Flow flow = entry.getFlow();
+                List<FlowOnNode> list = (map.containsKey(node)) ? map.get(node)
+                        : new ArrayList<FlowOnNode>();
+                list.add(reader.readFlow(node, flow));
+                map.put(node, list);
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public int getFlowsNumber(Node node) {
+        return reader.readAllFlows(node).size();
+    }
+
+    @Override
+    public NodeDescription getNodeDescription(Node node) {
+        return reader.readDescription(node);
+    }
+
+    @Override
+    public NodeConnectorStatistics getNodeConnectorStatistics(
+            NodeConnector nodeConnector) {
+        return reader.readNodeConnector(nodeConnector);
+    }
+
+    @Override
+    public List<NodeConnectorStatistics> getNodeConnectorStatistics(Node node) {
+        return reader.readNodeConnectors(node);
+    }
+}
diff --git a/opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/internal/Activator.java b/opendaylight/statisticsmanager/src/main/java/org/opendaylight/controller/statisticsmanager/internal/Activator.java
new file mode 100644 (file)
index 0000000..e26b6b8
--- /dev/null
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.statisticsmanager.internal;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.reader.IReadService;
+import org.opendaylight.controller.statisticsmanager.IStatisticsManager;
+import org.opendaylight.controller.statisticsmanager.StatisticsManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { StatisticsManager.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(StatisticsManager.class)) {
+            // export the service
+            c.setInterface(new String[] { IStatisticsManager.class.getName() },
+                    null);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IReadService.class).setCallbacks("setReaderService",
+                    "unsetReaderService").setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/statisticsmanager/src/test/java/org/opendaylight/controller/statisticsmanager/StatisticsManagerTest.java b/opendaylight/statisticsmanager/src/test/java/org/opendaylight/controller/statisticsmanager/StatisticsManagerTest.java
new file mode 100644 (file)
index 0000000..41c5f76
--- /dev/null
@@ -0,0 +1,28 @@
+\r
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.statisticsmanager;\r
+\r
+import org.junit.Test;\r
+\r
+public class StatisticsManagerTest {\r
+\r
+       @Test\r
+       public void test() {\r
+               StatisticsManager sm = new StatisticsManager();\r
+               \r
+               sm.init();\r
+               sm.start();\r
+               sm.stop();\r
+               sm.destroy();\r
+               \r
+               \r
+       }\r
+\r
+}\r
diff --git a/opendaylight/switchmanager/pom.xml b/opendaylight/switchmanager/pom.xml
new file mode 100755 (executable)
index 0000000..1d6f2e1
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>switchmanager</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+       <artifactId>maven-bundle-plugin</artifactId>
+       <version>2.3.6</version>
+       <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Export-Package>
+               org.opendaylight.controller.switchmanager
+           </Export-Package>
+           <Import-Package>
+               org.opendaylight.controller.clustering.services,
+            org.opendaylight.controller.configuration,         
+               org.opendaylight.controller.sal.core,
+            org.opendaylight.controller.sal.utils,
+            org.opendaylight.controller.sal.packet,
+            org.opendaylight.controller.sal.inventory,
+            org.slf4j,
+            org.apache.felix.dm,
+            org.eclipse.osgi.framework.console,
+            org.osgi.framework,
+            javax.xml.bind.annotation,
+            org.apache.commons.lang3.builder
+        </Import-Package>
+        <Bundle-Activator>
+               org.opendaylight.controller.switchmanager.internal.Activator
+        </Bundle-Activator>
+         </instructions>
+       </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+       <dependency>
+         <groupId>org.opendaylight.controller</groupId>
+         <artifactId>clustering.services</artifactId>
+         <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>configuration</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>      
+       <dependency>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>sal</artifactId>
+               <version>0.4.0-SNAPSHOT</version>
+       </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/IInventoryListener.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/IInventoryListener.java
new file mode 100644 (file)
index 0000000..73e3376
--- /dev/null
@@ -0,0 +1,46 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.util.Map;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+
+/**
+ * Primary purpose of this interface is to provide methods to listen to inventory changes
+ */
+public interface IInventoryListener {
+    /**
+     * This method is called when some properties of a node are added/deleted/changed.
+     *
+     * @param node                     {@link org.opendaylight.controller.sal.core.Node} being updated
+     * @param type             {@link org.opendaylight.controller.sal.core.UpdateType}
+     * @param propMap          map of {@link org.opendaylight.controller.sal.core.Property} such as
+     *                                                 {@link org.opendaylight.controller.sal.core.Name} and/or
+     *                                                 {@link org.opendaylight.controller.sal.core.Tier} etc.
+     */
+    public void notifyNode(Node node, UpdateType type,
+            Map<String, Property> propMap);
+
+    /**
+     * This method is called when some properties of a node connector are added/deleted/changed.
+     *
+     * @param nodeConnector    {@link org.opendaylight.controller.sal.core.NodeConnector} being updated
+     * @param type             {@link org.opendaylight.controller.sal.core.UpdateType}
+     * @param propMap          map of {@link org.opendaylight.controller.sal.core.Property} such as
+     *                                                 {@link org.opendaylight.controller.sal.core.Name} and/or
+     *                                                 {@link org.opendaylight.controller.sal.core.State} etc.
+     */
+    public void notifyNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Map<String, Property> propMap);
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISpanAware.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISpanAware.java
new file mode 100644 (file)
index 0000000..af1ab1c
--- /dev/null
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.util.List;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+/**
+ * The interface provides the methods for notifying when span ports
+ * are configured/unconfigured.
+ */
+public interface ISpanAware {
+    /**
+     * This method is called when list of ports in a node are added/deleted as span ports.
+     *
+     * @param node             {@link org.opendaylight.controller.sal.core.Node} being updated
+     * @param portList list of span {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param add              true if add; false if delete.
+     */
+    public void spanUpdate(Node node, List<NodeConnector> portList, boolean add);
+}
\ No newline at end of file
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISwitchManager.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISwitchManager.java
new file mode 100755 (executable)
index 0000000..15aeda3
--- /dev/null
@@ -0,0 +1,361 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.utils.Status;
+
+import org.opendaylight.controller.switchmanager.SpanConfig;
+import org.opendaylight.controller.switchmanager.Subnet;
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+
+/**
+ * Primary purpose of this interface is to provide methods for application to
+ * access various system resources and inventory data including nodes, node
+ * connectors and their properties, Layer3 configurations, Span configurations,
+ * node configurations, network device representations viewed by Controller Web
+ * applications.
+ */
+public interface ISwitchManager {
+    /**
+     * Add a subnet configuration
+     *
+     * @param  configObject refer to {@link Open Declaration org.opendaylight.controller.switchmanager.SubnetConfig}
+     * @return "Success" or failure reason
+     */
+    public Status addSubnet(SubnetConfig configObject);
+
+    /**
+     * Remove a subnet configuration
+     *
+     * @param  configObject    refer to {@link Open Declaration org.opendaylight.controller.switchmanager.SubnetConfig}
+     * @return "Success" or failure reason
+     */
+    public Status removeSubnet(SubnetConfig configObject);
+
+    /**
+     * Remove a subnet configuration given the name
+     *
+     * @param   name      subnet name
+     * @return "Success" or failure reason
+     */
+    public Status removeSubnet(String name);
+
+    /**
+     * Return a list of all known devices in the system
+     *
+     * @return returns a list of {@link org.opendaylight.controller.switchmanager.Switch}
+     */
+    public List<Switch> getNetworkDevices();
+
+    /**
+     * Return a list of subnet that were previously configured
+     *
+     * @return list of L3 interface {@link org.opendaylight.controller.switchmanager.SubnetConfig} configurations
+     */
+    public List<SubnetConfig> getSubnetsConfigList();
+
+    /**
+     * Return the subnet configuration
+     *
+     * @param   subnet      subnet
+     * @return a L3 interface {@link org.opendaylight.controller.switchmanager.SubnetConfig} configuration
+     */
+    public SubnetConfig getSubnetConfig(String subnet);
+
+    /**
+     * Return a subnet configuration given the network address
+     *
+     * @param networkAddress   the ip address in long format
+     * @return                                         the {@link org.opendaylight.controller.switchmanager.Subnet}
+     */
+    public Subnet getSubnetByNetworkAddress(InetAddress networkAddress);
+
+    /**
+     * Save the current switch configurations
+     *
+     * @return the status code
+     */
+    public Status saveSwitchConfig();
+
+    /**
+     * Add a span port configuration
+     *
+     * @param SpanConfig refer to {@link Open Declaration org.opendaylight.controller.switchmanager.SpanConfig}
+     * @return                         status code
+     */
+    public Status addSpanConfig(SpanConfig configObject);
+
+    /**
+     * Remove a span port configuration
+     *
+     * @param SpanConfig refer to {@link Open Declaration org.opendaylight.controller.switchmanager.SpanConfig}
+     * @return                         status code
+     */
+    public Status removeSpanConfig(SpanConfig cfgObject);
+
+    /**
+     * Return a list of span configurations that were configured previously
+     *
+     * @return list of {@link org.opendaylight.controller.switchmanager.SpanConfig} resources
+     */
+    public List<SpanConfig> getSpanConfigList();
+
+    /**
+     * Return the list of span ports of a given node
+     *
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @return the list of span {@link org.opendaylight.controller.sal.core.NodeConnector} of the node
+     */
+    public List<NodeConnector> getSpanPorts(Node node);
+
+    /**
+     * Update Switch specific configuration such as Switch Name and Tier
+     *
+     * @param cfgConfig refer to {@link Open Declaration org.opendaylight.controller.switchmanager.SwitchConfig}
+     */
+    public void updateSwitchConfig(SwitchConfig cfgObject);
+
+    /**
+     * Return the previously configured Switch Configuration given the node id
+     *
+     * @param nodeId Node Identifier as specified by {@link org.opendaylight.controller.sal.core.Node}
+     * @return {@link org.opendaylight.controller.switchmanager.SwitchConfig} resources
+     */
+    public SwitchConfig getSwitchConfig(String nodeId);
+
+    /**
+     * Add node connectors to a subnet
+     *
+     * @param name The subnet config name
+     * @param nodeConnectors nodePorts string specified by {@link Open Declaration org.opendaylight.controller.switchmanager.SubnetConfig}
+     * @return "Success" or failure reason
+     */
+    public Status addPortsToSubnet(String name, String nodeConnectors);
+
+    /**
+     * Remove node connectors from a subnet
+     *
+     * @param name                             the subnet config name
+     * @param nodeConnectors   nodePorts string specified by {@link Open Declaration org.opendaylight.controller.switchmanager.SubnetConfig}
+     * @return "Success" or failure reason
+     */
+    public Status removePortsFromSubnet(String name, String nodeConnectors);
+
+    /**
+     * Return the set of all the nodes
+     *
+     * @return set of {@link org.opendaylight.controller.sal.core.Node}
+     */
+    public Set<Node> getNodes();
+
+    /**
+     * Return all the properties of a node
+     *
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @return map of {@link org.opendaylight.controller.sal.core.Property} such as
+     *            {@link org.opendaylight.controller.sal.core.Name} and/or
+     *                    {@link org.opendaylight.controller.sal.core.Tier} etc.
+     */
+    public Map<String, Property> getNodeProps(Node node);
+
+    /**
+     * Return a specific property of a node given the property name
+     *
+     * @param node             {@link org.opendaylight.controller.sal.core.Node}
+     * @param propName         the property name specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return {@link org.opendaylight.controller.sal.core.Property}
+     */
+    public Property getNodeProp(Node node, String propName);
+
+    /**
+     * Set a specific property of a node
+     *
+     * @param node             {@link org.opendaylight.controller.sal.core.Node}
+     * @param prop             {@link org.opendaylight.controller.sal.core.Property}
+     */
+    public void setNodeProp(Node node, Property prop);
+
+    /**
+     * Remove a property of a node
+     * 
+     * @param nc               {@link org.opendaylight.controller.sal.core.Node}
+     * @param propName         the property name specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return success or failed reason
+     */
+    public Status removeNodeProp(Node node, String propName);
+
+    /**
+     * Remove all the properties of a node
+     * 
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @return success or failed reason
+     */
+    public Status removeNodeAllProps(Node node);
+
+    /**
+     * Return all the node connectors in up state for a given node
+     *
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @return set of {@link org.opendaylight.controller.sal.core.NodeConnector}
+     */
+    public Set<NodeConnector> getUpNodeConnectors(Node node);
+
+    /**
+     * Return all the node connectors including those special ones. Status of each node connector varies.
+     *
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @return all listed {@link org.opendaylight.controller.sal.core.NodeConnector}
+     */
+    public Set<NodeConnector> getNodeConnectors(Node node);
+
+    /**
+     * Return all the physical node connectors of a node. Status of each node connector varies.
+     *
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @return all physical {@link org.opendaylight.controller.sal.core.NodeConnector}
+     */
+    public Set<NodeConnector> getPhysicalNodeConnectors(Node node);
+
+    /**
+     * Return all the properties of a node connector
+     *
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @return map of {@link org.opendaylight.controller.sal.core.Property} such as
+     *                    {@link org.opendaylight.controller.sal.core.Name} and/or
+     *                    {@link org.opendaylight.controller.sal.core.State} etc.
+     */
+    public Map<String, Property> getNodeConnectorProps(
+            NodeConnector nodeConnector);
+
+    /**
+     * Return a specific property of a node connector given the property name
+     *
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param propName property name specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return {@link org.opendaylight.controller.sal.core.Property}
+     */
+    public Property getNodeConnectorProp(NodeConnector nodeConnector,
+            String propName);
+
+    /**
+     * Add a node connector and its property
+     *
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param prop {@link org.opendaylight.controller.sal.core.Property}
+     * @return success or failed reason
+     */
+    public Status addNodeConnectorProp(NodeConnector nodeConnector,
+            Property prop);
+
+    /**
+     * Remove a property of a node connector
+     * 
+     * @param nc {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param propName property name specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @return success or failed reason
+     */
+    public Status removeNodeConnectorProp(NodeConnector nc, String propName);
+
+    /**
+     * Remove all the properties of a node connector
+     * 
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @return success or failed reason
+     */
+    public Status removeNodeConnectorAllProps(NodeConnector nodeConnector);
+
+    /**
+     * Return the node connector given its name
+     *
+     * @param node                             {@link org.opendaylight.controller.sal.core.Node}
+     * @param nodeConnectorName node connector identifier specified by {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @return {@link org.opendaylight.controller.sal.core.NodeConnector}
+     */
+    public NodeConnector getNodeConnector(Node node, String nodeConnectorName);
+
+    /**
+     * Return whether the specified node connector is a special node port
+     * Example of node's special node connector are software stack, hardware path, controller...
+     *
+     * @param p {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @return true or false
+     */
+    public boolean isSpecial(NodeConnector p);
+
+    /**
+     * Check if the node connector is up running
+     *
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @return true or false
+     */
+    public Boolean isNodeConnectorEnabled(NodeConnector nodeConnector);
+
+    /**
+     * Return the list of mapping "nodeId"/"nodeName" for each connected node. 
+     * Each map contains two string keys: "nodeId", "nodeName". 
+     * Value associated to "nodeId" is the node id in string hex format. 
+     * Value associated to "nodeName" is the name of the node. If no name was set, 
+     * it will be the node id in string hex format.
+     *
+     * @return list of map
+     */
+    public List<Map<String, String>> getListNodeIdNameMap();
+
+    /**
+     * Return controller MAC address
+        *
+     * @return MAC address in byte array
+     */
+    public byte[] getControllerMAC();
+
+    /**
+     * Return MAC address for a given node
+     *
+     * @param node     {@link org.opendaylight.controller.sal.core.Node}
+     * @return MAC address in byte array
+     */
+    public byte[] getNodeMAC(Node node);
+
+    /**
+     * Return true if the host Refresh procedure (by sending ARP request probes
+     * to known hosts) is enabled. By default, the procedure is enabled. This can
+     * be overwritten by OSFI CLI "hostRefresh off".
+     *
+     * @return true if it is enabled; false if it's disabled.
+     */
+    public boolean isHostRefreshEnabled();
+
+    /**
+     * Return host refresh retry count
+     *
+     * @return host refresh retry count
+     */
+    public int getHostRetryCount();
+
+       /**
+        * Create a Name/Tier/Bandwidth Property object based on given property
+        * name and value. Other property types are not supported yet.
+        * 
+     * @param propName Name of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+     * @param propValue Value of the Property specified by {@link org.opendaylight.controller.sal.core.Property} and its extended classes
+        * @return {@link org.opendaylight.controller.sal.core.Property}
+        */
+    public Property createProperty(String propName, String propValue);
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISwitchManagerAware.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/ISwitchManagerAware.java
new file mode 100644 (file)
index 0000000..1b67e52
--- /dev/null
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import org.opendaylight.controller.sal.core.Node;
+
+import org.opendaylight.controller.switchmanager.Subnet;
+
+/**
+ * The interface class provides methods to notify listeners about subnet and
+ * mode changes.
+ */
+public interface ISwitchManagerAware {
+    /**
+     * The method is called when subnet is added/deleted
+     *
+     * @param sub {@link org.opendaylight.controller.switchmanager.Subnet}
+     * @param add true if add; false if delete.
+     */
+    public void subnetNotify(Subnet sub, boolean add);
+
+    /**
+     * The method is called when proactive/reactive mode is changed in a node
+     *
+     * @param node {@link org.opendaylight.controller.sal.core.Node}
+     * @param proactive true if mode is set as proactive; false if mode is reactive.
+     */
+    public void modeChangeNotify(Node node, boolean proactive);
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SpanConfig.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SpanConfig.java
new file mode 100644 (file)
index 0000000..21996c0
--- /dev/null
@@ -0,0 +1,131 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+import org.opendaylight.controller.sal.utils.GUIField;
+
+import org.opendaylight.controller.switchmanager.SpanConfig;
+
+/**
+ * The class represents a Span Port configuration for a network node.
+ */
+public class SpanConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final String guiFields[] = { GUIField.NODE.toString(),
+            GUIField.SPANPORTS.toString() };
+
+    // Order matters: JSP file expects following fields in the following order
+    private String nodeId;
+    private String spanPort;
+
+    public SpanConfig() {
+    }
+
+    public String getNodeId() {
+        return nodeId;
+    }
+
+    public String getSpanPort() {
+        return spanPort;
+    }
+
+    public Node getNode() {
+        return Node.fromString(nodeId);
+    }
+
+    private boolean hasValidNodeId() {
+        return (getNode() != null);
+    }
+
+    private boolean hasValidSpanPort() {
+        return (spanPort != null && !spanPort.isEmpty());
+    }
+
+    public boolean isValidConfig() {
+        return (hasValidNodeId() && hasValidSpanPort());
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public static ArrayList<String> getFieldsNames() {
+        ArrayList<String> fieldList = new ArrayList<String>();
+        for (Field fld : SpanConfig.class.getDeclaredFields()) {
+            fieldList.add(fld.getName());
+        }
+        //remove the two static fields
+        for (short i = 0; i < 2; i++) {
+            fieldList.remove(0);
+        }
+        return fieldList;
+    }
+
+    public static List<String> getGuiFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (String str : guiFields) {
+            fieldList.add(str);
+        }
+        return fieldList;
+    }
+
+    public ArrayList<NodeConnector> getPortArrayList() {
+        Node node = Node.fromString(nodeId);
+        ArrayList<NodeConnector> portList = new ArrayList<NodeConnector>();
+        String[] elemArray = spanPort.split(",");
+        for (String elem : elemArray) {
+            if (elem.contains("-")) {
+                String[] limits = elem.split("-");
+                for (short j = Short.valueOf(limits[0]); j <= Short
+                        .valueOf(limits[1]); j++) {
+                    try {
+                        portList.add(new NodeConnector(
+                                NodeConnectorIDType.OPENFLOW, Short.valueOf(j),
+                                node));
+                    } catch (ConstructionException e) {
+                        e.printStackTrace();
+                    }
+                }
+            } else {
+                try {
+                    portList.add(new NodeConnector(
+                            NodeConnectorIDType.OPENFLOW, Short.valueOf(elem),
+                            node));
+                } catch (NumberFormatException e) {
+                    e.printStackTrace();
+                } catch (ConstructionException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return portList;
+    }
+
+    public boolean matchNode(String nodeId) {
+        return this.nodeId.equals(nodeId);
+    }
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/Subnet.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/Subnet.java
new file mode 100644 (file)
index 0000000..551f072
--- /dev/null
@@ -0,0 +1,244 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+import org.opendaylight.controller.switchmanager.Subnet;
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+
+/**
+ * The class describes subnet information including L3 address, vlan and set of
+ * ports associated with the subnet.
+ */
+public class Subnet implements Serializable {
+    private static final long serialVersionUID = 1L;
+    // Key fields
+    private InetAddress networkAddress;
+    private short subnetMaskLength;
+    // Property fields
+    private short vlan;
+    private Set<NodeConnector> nodeConnectors;
+
+    public Subnet(InetAddress ip, short maskLen, short vlan) {
+        this.networkAddress = ip;
+        this.subnetMaskLength = maskLen;
+        this.vlan = vlan;
+        this.nodeConnectors = new HashSet<NodeConnector>();
+    }
+
+    public Subnet(SubnetConfig conf) {
+        networkAddress = conf.getIPnum();
+        subnetMaskLength = conf.getIPMaskLen();
+        nodeConnectors = conf.getSubnetNodeConnectors();
+    }
+
+    /**
+     * Add NodeConnectors to a subnet
+     *
+     * @param sp Set of NodeConnectors to add to the subnet
+     */
+    public void addNodeConnectors(Set<NodeConnector> sp) {
+        if (sp == null) {
+            return;
+        }
+
+        for (NodeConnector p : sp) {
+            this.nodeConnectors.add(p);
+        }
+    }
+
+    /**
+     * Delete NodeConnectors from subnet
+     *
+     * @param sp Set of NodeConnectors to add to the subnet
+     */
+    public void deleteNodeConnectors(Set<NodeConnector> sp) {
+        if (sp == null) {
+            return;
+        }
+        for (NodeConnector p : sp) {
+            this.nodeConnectors.remove(p);
+        }
+    }
+
+    /**
+     * Return the list of NodeConnectors configured for this subnet,
+     * could be also an empty set in case of all the known
+     * nodeconnectors.
+     *
+     *
+     * @return The list of NodeConnectors attached to the subnet
+     */
+    public Set<NodeConnector> getNodeConnectors() {
+        return this.nodeConnectors;
+    }
+
+    /**
+     * If the subnet has no node connectors attached to it then it
+     * means that is a whole L2 flat domain
+     *
+     *
+     * @return true if there are no node connectors configured for the
+     * subnet else false
+     */
+    public boolean isFlatLayer2() {
+        return nodeConnectors.isEmpty();
+    }
+
+    /**
+     * getter method
+     *
+     *
+     * @return the Network Address part of the subnet
+     */
+    public InetAddress getNetworkAddress() {
+        return networkAddress;
+    }
+
+    /**
+     * @param networkAddress the networkAddress to set
+     */
+    public Subnet setNetworkAddress(InetAddress networkAddress) {
+        this.networkAddress = networkAddress;
+        return this;
+    }
+
+    /**
+     * getter method
+     *
+     *
+     * @return the subnet mask length
+     */
+    public short getSubnetMaskLength() {
+        return this.subnetMaskLength;
+    }
+
+    public Subnet setSubnetMaskLength(short m) {
+        this.subnetMaskLength = m;
+        return this;
+    }
+
+    /*
+     * returns the prefix of a given IP by applying this subnet's mask
+     */
+    private InetAddress getPrefixForAddress(InetAddress ip) {
+        int bytes = this.subnetMaskLength / 8;
+        int bits = this.subnetMaskLength % 8;
+        byte modifiedByte;
+        byte[] sn = ip.getAddress();
+        if (bits > 0) {
+            modifiedByte = (byte) (sn[bytes] >> (8 - bits));
+            sn[bytes] = (byte) (modifiedByte << (8 - bits));
+            bytes++;
+        }
+        for (; bytes < sn.length; bytes++) {
+            sn[bytes] = (byte) (0);
+        }
+        try {
+            return InetAddress.getByAddress(sn);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+    public boolean isSubnetOf(InetAddress ip) {
+        if (ip == null)
+            return false;
+        InetAddress thisPrefix = getPrefixForAddress(this.networkAddress);
+        InetAddress otherPrefix = getPrefixForAddress(ip);
+        if ((thisPrefix == null) || (otherPrefix == null))
+            return false;
+        if (thisPrefix.equals(otherPrefix))
+            return true;
+        else
+            return false;
+    }
+
+    public short getVlan() {
+        return this.vlan;
+    }
+
+    public Subnet setVlan(short i) {
+        this.vlan = i;
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(networkAddress).append(
+                subnetMaskLength).toHashCode();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+        Subnet other = (Subnet) obj;
+        // Check only equality for the key fields
+        return new EqualsBuilder().append(networkAddress, other.networkAddress)
+                .append(subnetMaskLength, other.subnetMaskLength).isEquals();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return ("Subnet [networkAddress=" + networkAddress.getHostAddress()
+                + "/" + subnetMaskLength
+                + ((vlan == 0) ? "" : (" vlan=" + vlan)) + " "
+                + ((isFlatLayer2()) ? "{[*, *]}" : nodeConnectors.toString()) + "]");
+    }
+
+    public boolean hasNodeConnector(NodeConnector p) {
+        if (p == null) {
+            return false;
+        }
+        if (this.isFlatLayer2()) {
+            return true;
+        }
+        return this.nodeConnectors.contains(p);
+    }
+
+    public boolean isMutualExclusive(Subnet otherSubnet) {
+        if (this.networkAddress.getClass() != otherSubnet.networkAddress
+                .getClass())
+            return true;
+        if (this.isSubnetOf(otherSubnet.getNetworkAddress())) {
+            return false;
+        }
+        if (otherSubnet.isSubnetOf(this.getNetworkAddress())) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SubnetConfig.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SubnetConfig.java
new file mode 100644 (file)
index 0000000..1e974af
--- /dev/null
@@ -0,0 +1,275 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlAttribute;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.GUIField;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+
+/**
+ * The class represents a subnet configuration.
+ */
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class SubnetConfig implements Serializable {
+    //static fields are by default excluded by Gson parser
+    private static final long serialVersionUID = 1L;
+    private static final String regexIP = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+            + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])[/](\\d|[12]\\d|3[0-2])$";
+    private static final String prettyFields[] = { GUIField.NAME.toString(),
+            GUIField.GATEWAYIP.toString(), GUIField.NODEPORTS.toString() };
+
+    // Order matters: JSP file expects following fields in the
+    // following order
+    @XmlAttribute
+    private String name;
+    @XmlAttribute
+    private String subnet; // A.B.C.D/MM  Where A.B.C.D is the Default
+                           // Gateway IP (L3) or ARP Querier IP (L2
+    @XmlElement
+    private List<String> nodePorts; // datapath ID/port list:
+                                    // xx:xx:xx:xx:xx:xx:xx:xx/a,b,c-m,r-t,y
+
+    public SubnetConfig() {
+    }
+
+    public SubnetConfig(String desc, String sub, List<String> sp) {
+        name = desc;
+        subnet = sub;
+        nodePorts = sp;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<String> getNodePorts() {
+        return nodePorts;
+    }
+
+    public String getSubnet() {
+        return subnet;
+    }
+
+    public InetAddress getIPnum() {
+        InetAddress ip = null;
+        try {
+            ip = InetAddress.getByName(subnet.split("/")[0]);
+        } catch (UnknownHostException e1) {
+            return null;
+        }
+        return ip;
+    }
+
+    public Short getIPMaskLen() {
+        Short maskLen = 0;
+        if (hasValidIP()) {
+            String[] s = subnet.split("/");
+            maskLen = (s.length == 2) ? Short.valueOf(s[1]) : 32;
+        }
+        return maskLen;
+    }
+
+    public boolean isIPv6AddressValid() {
+        if (subnet == null)
+            return false;
+        String values[] = subnet.split("/");
+        try {
+            //when given an IP address, InetAddress.getByName validates the ip address
+            InetAddress addr = InetAddress.getByName(values[0]);
+            if (!(addr instanceof Inet6Address)) {
+                return false;
+            }
+        } catch (UnknownHostException ex) {
+            return false;
+        }
+        if (values.length >= 2) {
+            int prefix = Integer.valueOf(values[1]);
+            if ((prefix < 0) || (prefix > 128)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Set<Short> getPortList(String ports) {
+        /*
+         * example:
+         *     ports = "1,3,5-12"
+         *     elemArray = ["1" "3" "5-12"]
+         *     elem[2] = "5-12" --> limits = ["5" "12"]
+         *     portList = [1 3 5 6 7 8 9 10 11 12]
+         */
+        Set<Short> portList = new HashSet<Short>();
+        String[] elemArray = ports.split(",");
+        for (String elem : elemArray) {
+            if (elem.contains("-")) {
+                String[] limits = elem.split("-");
+                for (short j = Short.valueOf(limits[0]); j <= Short
+                        .valueOf(limits[1]); j++) {
+                    portList.add(Short.valueOf(j));
+                }
+            } else {
+                portList.add(Short.valueOf(elem));
+            }
+        }
+        return portList;
+    }
+
+    private boolean hasValidIP() {
+        if (subnet != null) {
+            if (subnet.matches(regexIP)) {
+                return true;
+            } else if (isIPv6AddressValid()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasValidPorts() {
+        for (String portSet : nodePorts) {
+            if (!portSet.contains("/")) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isValidSwitchPort(String sp) {
+        return sp.contains("/");
+    }
+
+    public boolean isValidConfig() {
+        return hasValidIP() && hasValidPorts();
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        /*
+         * Configuration will be stored in collection only if it is valid
+         * Hence we don't check here for uninitialized fields
+         */
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SubnetConfig that = (SubnetConfig) obj;
+        if (this.subnet.equals(that.subnet)) {
+            return true;
+        }
+        return false;
+    }
+
+    public static List<String> getFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (Field fld : SubnetConfig.class.getDeclaredFields()) {
+            fieldList.add(fld.getName());
+        }
+        //remove the four static fields
+        for (short i = 0; i < 3; i++) {
+            fieldList.remove(0);
+        }
+        return fieldList;
+    }
+
+    public static List<String> getGuiFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (String str : prettyFields) {
+            fieldList.add(str);
+        }
+        return fieldList;
+    }
+
+    //Utility method useful for adding to a passed Set all the
+    //NodeConnectors learnt from a string
+    private void getNodeConnectorsFromString(String codedNodeConnectors,
+            Set<NodeConnector> sp) {
+        if (codedNodeConnectors == null) {
+            return;
+        }
+        if (sp == null) {
+            return;
+        }
+        // codedNodeConnectors = xx:xx:xx:xx:xx:xx:xx:xx/a,b,c-m,r-t,y
+        String pieces[] = codedNodeConnectors.split("/");
+        for (Short port : getPortList(pieces[1])) {
+            Node n = Node.fromString(pieces[0]);
+            if (n == null) {
+                continue;
+            }
+            NodeConnector p = NodeConnectorCreator.createOFNodeConnector(port,
+                    n);
+            if (p == null) {
+                continue;
+            }
+            sp.add(p);
+        }
+    }
+
+    public Set<NodeConnector> getSubnetNodeConnectors() {
+        Set<NodeConnector> sp = new HashSet<NodeConnector>();
+        if (isGlobal())
+            return sp;
+        for (String str : nodePorts) {
+            getNodeConnectorsFromString(str, sp);
+        }
+        return sp;
+    }
+
+    public Set<NodeConnector> getNodeConnectors(String codedNodeConnectors) {
+        // codedNodeConnectors = xx:xx:xx:xx:xx:xx:xx:xx/a,b,c-m,r-t,y
+        Set<NodeConnector> sp = new HashSet<NodeConnector>();
+        getNodeConnectorsFromString(codedNodeConnectors, sp);
+        return sp;
+    }
+
+    public boolean isGlobal() {
+        // If no ports are specified to be part of the domain, then it's a global domain IP
+        return (nodePorts == null || nodePorts.isEmpty());
+    }
+
+    public void addNodeConnectors(String sp) {
+        nodePorts.add(sp);
+    }
+
+    public void removeNodeConnectors(String sp) {
+        nodePorts.remove(sp);
+    }
+
+    public String toString() {
+        return ("Subnet Config [Description=" + name + " Subnet=" + subnet
+                + " NodeConnectors=" + nodePorts + "]");
+    }
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/Switch.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/Switch.java
new file mode 100755 (executable)
index 0000000..d13c042
--- /dev/null
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+/**
+ * The class describes switch related information including L2 address, ports,
+ * span ports and associated node representation
+ */
+public class Switch implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private byte[] dataLayerAddress;
+    private Set<NodeConnector> nodeConnectors;
+    private List<NodeConnector> spanPorts;
+    private Node node;
+
+    /*
+     * As we are adding switches on per event basis in a map, we do not need a default constructor
+     * This way we can keep the validations internal, in the proper constructor
+    public Switch() {
+        this.swPorts = new HashSet<ONESwitchPortTuple>();
+        this.spanPorts = new ArrayList<Short>(2);
+    }
+     */
+
+    public Switch(Node node) {
+        this.node = node;
+        this.nodeConnectors = new HashSet<NodeConnector>();
+        this.spanPorts = new ArrayList<NodeConnector>(2);
+        this.dataLayerAddress = deriveMacAddress();
+    }
+
+    /**
+     * @return the dataLayerAddress
+     */
+    public byte[] getDataLayerAddress() {
+        return dataLayerAddress;
+    }
+
+    /**
+     * @param dataLayerAddress the dataLayerAddress to set
+     */
+    public void setDataLayerAddress(byte[] dataLayerAddress) {
+        this.dataLayerAddress = (dataLayerAddress == null) ? null
+                : dataLayerAddress.clone();
+    }
+
+    /**
+     * @return the nodeConnectors
+     */
+    public Set<NodeConnector> getNodeConnectors() {
+        return nodeConnectors;
+    }
+
+    /**
+     * @param nodeConnectors nodeConnector set
+     */
+    public void setNodeConnectors(Set<NodeConnector> nodeConnectors) {
+        this.nodeConnectors = nodeConnectors;
+    }
+
+    public void addNodeConnector(NodeConnector nodeConnector) {
+        if (!nodeConnectors.contains(nodeConnector)) {
+            nodeConnectors.add(nodeConnector);
+        }
+    }
+
+    public void removeNodeConnector(NodeConnector nodeConnector) {
+        nodeConnectors.remove(nodeConnector);
+    }
+
+    public List<NodeConnector> getSpanPorts() {
+        return spanPorts;
+    }
+
+    public Node getNode() {
+        return node;
+    }
+
+    public void setNode(Node node) {
+        this.node = node;
+    }
+
+    private byte[] deriveMacAddress() {
+        long dpid = (Long) this.node.getID();
+        byte[] mac = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+        for (short i = 0; i < 6; i++) {
+            mac[5 - i] = (byte) dpid;
+            dpid >>= 8;
+        }
+
+        return mac;
+    }
+
+    public void addSpanPorts(List<NodeConnector> portList) {
+        for (NodeConnector port : portList) {
+            spanPorts.add(port);
+        }
+    }
+
+    public void removeSpanPorts(List<NodeConnector> portList) {
+        for (NodeConnector port : portList) {
+            spanPorts.remove(port);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public String toString() {
+        return "Switch[" + ReflectionToStringBuilder.toString(this) + "]";
+    }
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SwitchConfig.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/SwitchConfig.java
new file mode 100644 (file)
index 0000000..550ca69
--- /dev/null
@@ -0,0 +1,65 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+/**
+ * The class describes a switch configuration including node identifier, node
+ * name, tier number and proactive/reactive mode.
+ */
+public class SwitchConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+    String nodeId;
+    String nodeName;
+    String tier;
+    String mode;
+
+    public SwitchConfig(String nodeId, String nodeName, String tier, String mode) {
+        super();
+        this.nodeId = nodeId;
+        this.nodeName = nodeName;
+        this.tier = tier;
+        this.mode = mode;
+    }
+
+    public String getNodeId() {
+        return nodeId;
+    }
+
+    public String getNodeName() {
+        return nodeName;
+    }
+
+    public String getTier() {
+        return tier;
+    }
+
+    public String getMode() {
+        return mode;
+    }
+
+    public static long getSerialversionuid() {
+        return serialVersionUID;
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/internal/Activator.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/internal/Activator.java
new file mode 100644 (file)
index 0000000..d4511ff
--- /dev/null
@@ -0,0 +1,122 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.internal;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.inventory.IInventoryService;
+import org.opendaylight.controller.sal.inventory.IListenInventoryUpdates;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISpanAware;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * SwitchManager Bundle Activator
+ *
+ *
+ */
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { SwitchManagerImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(SwitchManagerImpl.class)) {
+            Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+            Set<String> propSet = new HashSet<String>();
+            propSet.add("switchmanager.configSaveEvent");
+            props.put("cachenames", propSet);
+            // export the service
+            c.setInterface(new String[] {
+                    IListenInventoryUpdates.class.getName(),
+                    ISwitchManager.class.getName(),
+                    ICacheUpdateAware.class.getName(),
+                    IConfigurationContainerAware.class.getName() }, props);
+
+            // Now lets add a service dependency to make sure the
+            // provider of service exists
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IInventoryService.class).setCallbacks(
+                    "setInventoryService", "unsetInventoryService")
+                    .setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManagerAware.class).setCallbacks(
+                    "setSwitchManagerAware", "unsetSwitchManagerAware")
+                    .setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IInventoryListener.class).setCallbacks(
+                    "setInventoryListener", "unsetInventoryListener")
+                    .setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISpanAware.class).setCallbacks("setSpanAware",
+                    "unsetSpanAware").setRequired(false));
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IClusterContainerServices.class).setCallbacks(
+                    "setClusterContainerService",
+                    "unsetClusterContainerService").setRequired(true));
+        }
+    }
+}
diff --git a/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/internal/SwitchManagerImpl.java b/opendaylight/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/internal/SwitchManagerImpl.java
new file mode 100755 (executable)
index 0000000..b36d8c1
--- /dev/null
@@ -0,0 +1,1774 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.internal;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.felix.dm.Component;
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.Config;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+import org.opendaylight.controller.sal.core.Tier;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.inventory.IInventoryService;
+import org.opendaylight.controller.sal.inventory.IListenInventoryUpdates;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.IObjectReader;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.sal.utils.ObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectWriter;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISpanAware;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
+import org.opendaylight.controller.switchmanager.SpanConfig;
+import org.opendaylight.controller.switchmanager.Subnet;
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The class describes SwitchManager which is the central repository of all the
+ * inventory data including nodes, node connectors, properties attached, Layer3
+ * configurations, Span configurations, node configurations, network device
+ * representations viewed by Controller Web applications. One SwitchManager
+ * instance per container of the network. All the node/nodeConnector properties
+ * are maintained in the default container only.
+ */
+public class SwitchManagerImpl implements ISwitchManager,
+        IConfigurationContainerAware, IObjectReader,
+        ICacheUpdateAware<Long, String>, IListenInventoryUpdates,
+        CommandProvider {
+    private static Logger log = LoggerFactory
+            .getLogger(SwitchManagerImpl.class);
+    private static String ROOT = GlobalConstants.STARTUPHOME.toString();
+    private static final String SAVE = "Save";
+    private String subnetFileName = null, spanFileName = null,
+            switchConfigFileName = null;
+    private List<NodeConnector> spanNodeConnectors = new CopyOnWriteArrayList<NodeConnector>();
+    private ConcurrentMap<InetAddress, Subnet> subnets; // set of Subnets keyed by the InetAddress
+    private ConcurrentMap<String, SubnetConfig> subnetsConfigList;
+    private ConcurrentMap<Integer, SpanConfig> spanConfigList;
+    private ConcurrentMap<String, SwitchConfig> nodeConfigList; // manually configured parameters for the node, like name and tier
+    private ConcurrentMap<Long, String> configSaveEvent;
+    private ConcurrentMap<Node, Map<String, Property>> nodeProps; // properties are maintained in default container only
+    private ConcurrentMap<NodeConnector, Map<String, Property>> nodeConnectorProps; // properties are maintained in default container only
+    private ConcurrentMap<Node, Map<String, NodeConnector>> nodeConnectorNames;
+    private IInventoryService inventoryService;
+    private Set<ISwitchManagerAware> switchManagerAware = Collections
+            .synchronizedSet(new HashSet<ISwitchManagerAware>());
+    private Set<IInventoryListener> inventoryListeners = Collections
+            .synchronizedSet(new HashSet<IInventoryListener>());
+    private Set<ISpanAware> spanAware = Collections
+            .synchronizedSet(new HashSet<ISpanAware>());
+    private byte[] MAC;
+    private static boolean hostRefresh = true;
+    private int hostRetryCount = 5;
+    private IClusterContainerServices clusterContainerService = null;
+    private String containerName = null;
+    private boolean isDefaultContainer = true;
+    
+    public enum ReasonCode {
+        SUCCESS("Success"), FAILURE("Failure"), INVALID_CONF(
+                "Invalid Configuration"), EXIST("Entry Already Exist"), CONFLICT(
+                "Configuration Conflict with Existing Entry");
+
+        private String name;
+
+        private ReasonCode(String name) {
+            this.name = name;
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    public void notifySubnetChange(Subnet sub, boolean add) {
+        synchronized (switchManagerAware) {
+            for (Object subAware : switchManagerAware) {
+                try {
+                    ((ISwitchManagerAware) subAware).subnetNotify(sub, add);
+                } catch (Exception e) {
+                    log.error("Failed to notify Subnet change", e);
+                }
+            }
+        }
+    }
+
+    public void notifySpanPortChange(Node node, List<NodeConnector> ports,
+            boolean add) {
+        synchronized (spanAware) {
+            for (Object sa : spanAware) {
+                try {
+                    ((ISpanAware) sa).spanUpdate(node, ports, add);
+                } catch (Exception e) {
+                    log.error("Failed to notify Span Interface change", e);
+                }
+            }
+        }
+    }
+
+    private void notifyModeChange(Node node, boolean proactive) {
+        synchronized (switchManagerAware) {
+            for (ISwitchManagerAware service : switchManagerAware) {
+                try {
+                    service.modeChangeNotify(node, proactive);
+                } catch (Exception e) {
+                    log.error("Failed to notify Subnet change", e);
+                }
+            }
+        }
+    }
+
+    public void startUp() {
+        // Initialize configuration file names
+        subnetFileName = ROOT + "subnets" + this.getContainerName() + ".conf";
+        spanFileName = ROOT + "spanPorts_" + this.getContainerName() + ".conf";
+        switchConfigFileName = ROOT + "switchConfig_" + this.getContainerName()
+                + ".conf";
+
+        // Instantiate cluster synced variables
+        allocateCaches();
+        retrieveCaches();
+
+        /*
+         * Read startup and build database if we have not already gotten the
+         * configurations synced from another node
+         */
+        if (subnetsConfigList.isEmpty())
+            loadSubnetConfiguration();
+        if (spanConfigList.isEmpty())
+            loadSpanConfiguration();
+        if (nodeConfigList.isEmpty())
+            loadSwitchConfiguration();
+
+        MAC = getHardwareMAC();
+    }
+
+    public void shutDown() {
+        destroyCaches(this.getContainerName());
+    }
+
+    @SuppressWarnings("deprecation")
+       private void allocateCaches() {
+        if (this.clusterContainerService == null) {
+            log.info("un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+
+        try {
+            clusterContainerService.createCache(
+                    "switchmanager.subnetsConfigList", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache("switchmanager.spanConfigList",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache("switchmanager.nodeConfigList",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache("switchmanager.subnets",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache(
+                    "switchmanager.configSaveEvent", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache("switchmanager.nodeProps",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache(
+                    "switchmanager.nodeConnectorProps", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+            clusterContainerService.createCache(
+                    "switchmanager.nodeConnectorNames", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheConfigException cce) {
+            log.error("\nCache configuration invalid - check cache mode");
+        } catch (CacheExistException ce) {
+            log.error("\nCache already exits - destroy and recreate if needed");
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    private void retrieveCaches() {
+        if (this.clusterContainerService == null) {
+            log
+                    .info("un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+
+        subnetsConfigList = (ConcurrentMap<String, SubnetConfig>) clusterContainerService
+                .getCache("switchmanager.subnetsConfigList");
+        if (subnetsConfigList == null) {
+            log.error("\nFailed to get cache for subnetsConfigList");
+        }
+
+        spanConfigList = (ConcurrentMap<Integer, SpanConfig>) clusterContainerService
+                .getCache("switchmanager.spanConfigList");
+        if (spanConfigList == null) {
+            log.error("\nFailed to get cache for spanConfigList");
+        }
+
+        nodeConfigList = (ConcurrentMap<String, SwitchConfig>) clusterContainerService
+                .getCache("switchmanager.nodeConfigList");
+        if (nodeConfigList == null) {
+            log.error("\nFailed to get cache for nodeConfigList");
+        }
+
+        subnets = (ConcurrentMap<InetAddress, Subnet>) clusterContainerService
+                .getCache("switchmanager.subnets");
+        if (subnets == null) {
+            log.error("\nFailed to get cache for subnets");
+        }
+
+        configSaveEvent = (ConcurrentMap<Long, String>) clusterContainerService
+                .getCache("switchmanager.configSaveEvent");
+        if (configSaveEvent == null) {
+            log.error("\nFailed to get cache for configSaveEvent");
+        }
+
+        nodeProps = (ConcurrentMap<Node, Map<String, Property>>) clusterContainerService
+                .getCache("switchmanager.nodeProps");
+        if (nodeProps == null) {
+            log.error("\nFailed to get cache for nodeProps");
+        }
+
+        nodeConnectorProps = (ConcurrentMap<NodeConnector, Map<String, Property>>) clusterContainerService
+                .getCache("switchmanager.nodeConnectorProps");
+        if (nodeConnectorProps == null) {
+            log.error("\nFailed to get cache for nodeConnectorProps");
+        }
+
+        nodeConnectorNames = (ConcurrentMap<Node, Map<String, NodeConnector>>) clusterContainerService
+                .getCache("switchmanager.nodeConnectorNames");
+        if (nodeConnectorNames == null) {
+            log.error("\nFailed to get cache for nodeConnectorNames");
+        }
+    }
+
+    void nonClusterObjectCreate() {
+        subnetsConfigList = new ConcurrentHashMap<String, SubnetConfig>();
+        spanConfigList = new ConcurrentHashMap<Integer, SpanConfig>();
+        nodeConfigList = new ConcurrentHashMap<String, SwitchConfig>();
+        subnets = new ConcurrentHashMap<InetAddress, Subnet>();
+        configSaveEvent = new ConcurrentHashMap<Long, String>();
+        nodeProps = new ConcurrentHashMap<Node, Map<String, Property>>();
+        nodeConnectorProps = new ConcurrentHashMap<NodeConnector, Map<String, Property>>();
+        nodeConnectorNames = new ConcurrentHashMap<Node, Map<String, NodeConnector>>();
+    }
+
+    @SuppressWarnings("deprecation")
+       private void destroyCaches(String container) {
+        if (this.clusterContainerService == null) {
+            log
+                    .info("un-initialized clusterContainerService, can't create cache");
+            return;
+        }
+
+        clusterContainerService.destroyCache("switchmanager.subnetsConfigList");
+        clusterContainerService.destroyCache("switchmanager.spanConfigList");
+        clusterContainerService.destroyCache("switchmanager.nodeConfigList");
+        clusterContainerService.destroyCache("switchmanager.subnets");
+        clusterContainerService.destroyCache("switchmanager.configSaveEvent");
+        clusterContainerService.destroyCache("switchmanager.nodeProps");
+        clusterContainerService
+                .destroyCache("switchmanager.nodeConnectorProps");
+        clusterContainerService
+                .destroyCache("switchmanager.nodeConnectorNames");
+        nonClusterObjectCreate();
+    }
+
+    public List<SubnetConfig> getSubnetsConfigList() {
+        return new ArrayList<SubnetConfig>(subnetsConfigList.values());
+    }
+
+    @Override
+    public SubnetConfig getSubnetConfig(String subnet) {
+        return subnetsConfigList.get(subnet);
+    }
+
+    private List<SpanConfig> getSpanConfigList(Node node) {
+        List<SpanConfig> confList = new ArrayList<SpanConfig>();
+        String nodeId = node.toString();
+        for (SpanConfig conf : spanConfigList.values()) {
+            if (conf.matchNode(nodeId)) {
+                confList.add(conf);
+            }
+        }
+        return confList;
+    }
+
+    public List<SwitchConfig> getNodeConfigList() {
+        return new ArrayList<SwitchConfig>(nodeConfigList.values());
+    }
+
+    public SwitchConfig getSwitchConfig(String switchId) {
+        return nodeConfigList.get(switchId);
+    }
+
+    public Switch getSwitchByNode(Node node) {
+        Switch sw = new Switch(node);
+        sw.setNode(node);
+
+        Set<NodeConnector> ncSet = getPhysicalNodeConnectors(node);
+        sw.setNodeConnectors(ncSet);
+
+        List<NodeConnector> ncList = new ArrayList<NodeConnector>();
+        for (NodeConnector nodeConnector : ncSet) {
+            if (spanNodeConnectors.contains(nodeConnector)) {
+                ncList.add(nodeConnector);
+            }
+        }
+        sw.addSpanPorts(ncList);
+
+        return sw;
+    }
+
+    public List<Switch> getNetworkDevices() {
+        Set<Node> nodeSet = getNodes();
+        List<Switch> swList = new ArrayList<Switch>();
+        if (nodeSet != null) {
+            for (Node node : nodeSet) {
+                swList.add(getSwitchByNode(node));
+            }
+        }
+
+        return swList;
+    }
+
+    private void updateConfig(SubnetConfig conf, boolean add) {
+        if (add) {
+            subnetsConfigList.put(conf.getName(), conf);
+        } else {
+            subnetsConfigList.remove(conf.getName());
+        }
+    }
+
+    private void updateDatabase(SubnetConfig conf, boolean add) {
+        Subnet subnet = subnets.get(conf.getIPnum());
+        if (add) {
+            if (subnet == null) {
+                subnet = new Subnet(conf);
+            }
+            // In case of API3 call we may receive the ports along with the
+            // subnet creation
+            if (!conf.isGlobal()) {
+                Set<NodeConnector> sp = conf.getSubnetNodeConnectors();
+                subnet.addNodeConnectors(sp);
+            }
+            subnets.put(conf.getIPnum(), subnet);
+        } else { // This is the deletion of the whole subnet
+            if (subnet == null)
+                return;
+            subnets.remove(conf.getIPnum());
+        }
+    }
+
+    private Status semanticCheck(SubnetConfig conf) {
+        Subnet newSubnet = new Subnet(conf);
+        Set<InetAddress> IPs = subnets.keySet();
+        if (IPs == null) {
+               return new Status(StatusCode.SUCCESS, null);
+        }
+        for (InetAddress i : IPs) {
+            Subnet existingSubnet = subnets.get(i);
+            if ((existingSubnet != null)
+                    && !existingSubnet.isMutualExclusive(newSubnet)) {
+                return new Status(StatusCode.CONFLICT, null);
+            }
+        }
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private Status addRemoveSubnet(SubnetConfig conf, boolean add) {
+        // Valid config check
+        if (!conf.isValidConfig()) {
+               String msg = "Invalid Subnet configuration";
+            log.warn(msg);
+            return new Status(StatusCode.BADREQUEST, msg);
+        }
+
+        if (add) {
+            // Presence check
+            if (subnetsConfigList.containsKey(conf.getName())) {
+               return new Status(StatusCode.CONFLICT, 
+                               "Same subnet config already exists");
+            }
+            // Semantyc check
+            Status rc = semanticCheck(conf);
+            if (!rc.isSuccess()) {
+                return rc;
+            }
+        }
+        // Update Configuration
+        updateConfig(conf, add);
+
+        // Update Database
+        updateDatabase(conf, add);
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    /**
+     * Adds Subnet configured in GUI or API3
+     */
+    public Status addSubnet(SubnetConfig conf) {
+        return this.addRemoveSubnet(conf, true);
+    }
+
+    @Override
+    public Status removeSubnet(SubnetConfig conf) {
+        return this.addRemoveSubnet(conf, false);
+    }
+
+    @Override
+    public Status removeSubnet(String name) {
+        SubnetConfig conf = subnetsConfigList.get(name);
+        if (conf == null) {
+            return new Status(StatusCode.SUCCESS, "Subnet not present");
+        }
+        return this.addRemoveSubnet(conf, false);
+    }
+
+    @Override
+    public Status addPortsToSubnet(String name, String switchPorts) {
+        // Update Configuration
+        SubnetConfig conf = subnetsConfigList.get(name);
+        if (conf == null) {
+            return new Status(StatusCode.NOTFOUND, "Subnet does not exist");
+        }
+        if (!conf.isValidSwitchPort(switchPorts)) {
+               return new Status(StatusCode.BADREQUEST, "Invalid switchports");
+        }
+
+        conf.addNodeConnectors(switchPorts);
+
+        // Update Database
+        Subnet sub = subnets.get(conf.getIPnum());
+        Set<NodeConnector> sp = conf.getNodeConnectors(switchPorts);
+        sub.addNodeConnectors(sp);
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public Status removePortsFromSubnet(String name, String switchPorts) {
+        // Update Configuration
+        SubnetConfig conf = subnetsConfigList.get(name);
+        if (conf == null) {
+               return new Status(StatusCode.NOTFOUND, "Subnet does not exist");
+        }
+        conf.removeNodeConnectors(switchPorts);
+
+        // Update Database
+        Subnet sub = subnets.get(conf.getIPnum());
+        Set<NodeConnector> sp = conf.getNodeConnectors(switchPorts);
+        sub.deleteNodeConnectors(sp);
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    public String getContainerName() {
+        if (containerName == null) {
+            return GlobalConstants.DEFAULT.toString();
+        }
+        return containerName;
+    }
+
+    @Override
+    public Subnet getSubnetByNetworkAddress(InetAddress networkAddress) {
+        Subnet sub;
+        Set<InetAddress> indices = subnets.keySet();
+        for (InetAddress i : indices) {
+            sub = subnets.get(i);
+            if (sub.isSubnetOf(networkAddress)) {
+                return sub;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Object readObject(ObjectInputStream ois)
+            throws FileNotFoundException, IOException, ClassNotFoundException {
+        // Perform the class deserialization locally, from inside the package
+        // where the class is defined
+        return ois.readObject();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadSubnetConfiguration() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<Integer, SubnetConfig> confList = (ConcurrentMap<Integer, SubnetConfig>) objReader
+                .read(this, subnetFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (SubnetConfig conf : confList.values()) {
+            addSubnet(conf);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadSpanConfiguration() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<Integer, SpanConfig> confList = (ConcurrentMap<Integer, SpanConfig>) objReader
+                .read(this, spanFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (SpanConfig conf : confList.values()) {
+            addSpanConfig(conf);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadSwitchConfiguration() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<String, SwitchConfig> confList = (ConcurrentMap<String, SwitchConfig>) objReader
+                .read(this, switchConfigFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (SwitchConfig conf : confList.values()) {
+            updateSwitchConfig(conf);
+        }
+    }
+
+    public void updateSwitchConfig(SwitchConfig cfgObject) {
+        boolean modeChange = false;
+
+        SwitchConfig sc = nodeConfigList.get(cfgObject.getNodeId());
+        if ((sc == null) || !cfgObject.getMode().equals(sc.getMode()))
+            modeChange = true;
+
+        nodeConfigList.put(cfgObject.getNodeId(), cfgObject);
+        try {
+            // update default container only
+            if (isDefaultContainer) {
+                String nodeId = cfgObject.getNodeId();
+                Node node = Node.fromString(nodeId);
+                Map<String, Property> propMap;
+                if (nodeProps.get(node) != null) {
+                    propMap = nodeProps.get(node);
+                } else {
+                    propMap = new HashMap<String, Property>();
+                }
+                Property name = new Name(cfgObject.getNodeName());
+                propMap.put(name.getName(), name);
+                Property tier = new Tier(Integer.parseInt(cfgObject.getTier()));
+                propMap.put(tier.getName(), tier);
+                addNodeProps(node, propMap);
+
+                log.info("Set Node {}'s Mode to {}", nodeId, cfgObject
+                        .getMode());
+
+                if (modeChange) {
+                    notifyModeChange(node, (Integer.parseInt(cfgObject
+                            .getMode()) != 0));
+                }
+            }
+        } catch (Exception e) {
+            log.debug("updateSwitchConfig: {}", e);
+        }
+    }
+
+    @Override
+    public Status saveSwitchConfig() {
+        // Publish the save config event to the cluster nodes
+        configSaveEvent.put(new Date().getTime(), SAVE);
+        return saveSwitchConfigInternal();
+    }
+
+    public Status saveSwitchConfigInternal() {
+        Status retS = null, retP = null;
+        ObjectWriter objWriter = new ObjectWriter();
+
+        retS = objWriter.write(new ConcurrentHashMap<String, SubnetConfig>(
+                subnetsConfigList), subnetFileName);
+        retP = objWriter.write(new ConcurrentHashMap<Integer, SpanConfig>(
+                spanConfigList), spanFileName);
+        retS = objWriter.write(new ConcurrentHashMap<String, SwitchConfig>(
+                nodeConfigList), switchConfigFileName);
+
+        if (retS.equals(retP)) {
+            if (retS.isSuccess()) {
+                return retS;
+            } else {
+                return new Status(StatusCode.INTERNALERROR, "Save failed");
+            }
+        } else {
+            return new Status(StatusCode.INTERNALERROR, "Partial save failure");
+        }
+    }
+
+    @Override
+    public List<SpanConfig> getSpanConfigList() {
+        return new ArrayList<SpanConfig>(spanConfigList.values());
+    }
+
+    @Override
+    public Status addSpanConfig(SpanConfig conf) {
+        // Valid config check
+        if (!conf.isValidConfig()) {
+               String msg = "Invalid Span configuration";
+            log.warn(msg);
+            return new Status(StatusCode.BADREQUEST, msg);
+        }
+
+        // Presence check
+        if (spanConfigList.containsKey(conf.hashCode())) {
+               return new Status(StatusCode.CONFLICT, "Same span config exists");
+        }
+
+        // Update database and notify clients
+        addSpanPorts(conf.getNode(), conf.getPortArrayList());
+
+        // Update configuration
+        spanConfigList.put(conf.hashCode(), conf);
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public Status removeSpanConfig(SpanConfig conf) {
+        removeSpanPorts(conf.getNode(), conf.getPortArrayList());
+
+        // Update configuration
+        spanConfigList.remove(conf.hashCode());
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public List<NodeConnector> getSpanPorts(Node node) {
+        List<NodeConnector> ncList = new ArrayList<NodeConnector>();
+
+        for (NodeConnector nodeConnector : spanNodeConnectors) {
+            if (nodeConnector.getNode().equals(node)) {
+                ncList.add(nodeConnector);
+            }
+        }
+        return ncList;
+    }
+
+    @Override
+    public void entryCreated(Long key, String cacheName, boolean local) {
+    }
+
+    @Override
+    public void entryUpdated(Long key, String new_value, String cacheName,
+            boolean originLocal) {
+        saveSwitchConfigInternal();
+    }
+
+    @Override
+    public void entryDeleted(Long key, String cacheName, boolean originLocal) {
+    }
+
+    private void addNode(Node node, Set<Property> props) {
+        log.trace("{} added", node);
+        if (nodeProps == null)
+            return;
+
+        Map<String, Property> propMap;
+        if (nodeProps.get(node) != null) {
+            propMap = nodeProps.get(node);
+        } else {
+            propMap = new HashMap<String, Property>();
+        }
+
+        // copy node properties from plugin
+        if (props != null) {
+            for (Property prop : props) {
+                propMap.put(prop.getName(), prop);
+            }
+        }
+
+        // copy node properties from config
+        if (nodeConfigList != null) {
+            String nodeId = node.getNodeIDString();
+            for (SwitchConfig conf : nodeConfigList.values()) {
+                if (conf.getNodeId().equals(nodeId)) {
+                    Property name = new Name(conf.getNodeName());
+                    propMap.put(name.getName(), name);
+                    Property tier = new Tier(Integer.parseInt(conf.getTier()));
+                    propMap.put(tier.getName(), tier);
+                    break;
+                }
+            }
+        }
+        addNodeProps(node, propMap);
+
+        // check if span ports are configed
+        addSpanPorts(node);
+
+        /* notify node listeners */
+        notifyNode(node, UpdateType.ADDED, propMap);
+    }
+
+    private void removeNode(Node node) {
+        log.trace("{} removed", node);
+        if (nodeProps == null)
+            return;
+        nodeProps.remove(node);
+
+        // check if span ports need to be cleaned up
+        removeSpanPorts(node);
+
+        /* notify node listeners */
+        notifyNode(node, UpdateType.REMOVED, null);
+    }
+
+    private void updateNode(Node node, Set<Property> props) {
+        log.trace("{} updated", node);
+        if (nodeProps == null) {
+            return;
+        }
+
+        Map<String, Property> propMap;
+        if (nodeProps.get(node) != null) {
+            propMap = nodeProps.get(node);
+        } else {
+            propMap = new HashMap<String, Property>();
+        }
+
+        // copy node properties from plugin
+        if (props != null) {
+            for (Property prop : props) {
+                propMap.put(prop.getName(), prop);
+            }
+        }
+        addNodeProps(node, propMap);
+
+        /* notify node listeners */
+        notifyNode(node, UpdateType.CHANGED, propMap);
+    }
+
+    @Override
+    public void updateNode(Node node, UpdateType type, Set<Property> props) {
+        switch (type) {
+        case ADDED:
+            addNode(node, props);
+            break;
+        case CHANGED:
+            updateNode(node, props);
+            break;
+        case REMOVED:
+            removeNode(node);
+            break;
+        default:
+            break;
+        }
+    }
+
+    @Override
+    public void updateNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Set<Property> props) {
+        Node node = nodeConnector.getNode();
+        Map<String, Property> propMap = new HashMap<String, Property>();
+
+        log.trace("{} {}", nodeConnector, type);
+
+        if (nodeConnectorProps == null)
+            return;
+
+        switch (type) {
+        case ADDED:
+        case CHANGED:
+            if (props != null) {
+                for (Property prop : props) {
+                    addNodeConnectorProp(nodeConnector, prop);
+                    propMap.put(prop.getName(), prop);
+                }
+            } else {
+                addNodeConnectorProp(nodeConnector, null);
+                addNodeProps(node, null);
+            }
+
+            // check if span is configed
+            addSpanPort(nodeConnector);
+            break;
+        case REMOVED:
+            removeNodeConnectorAllProps(nodeConnector);
+            removeNodeProps(node);
+
+            // clean up span config
+            removeSpanPort(nodeConnector);
+            break;
+        default:
+            break;
+        }
+
+        notifyNodeConnector(nodeConnector, type, propMap);
+    }
+
+    @Override
+    public Set<Node> getNodes() {
+        return (nodeProps != null) ? new HashSet<Node>(nodeProps.keySet())
+                : null;
+    }
+
+    /*
+     * test utility function which assumes all nodes are OF nodes
+     */
+    private Node getNode(Long id) {
+        Set<Node> nodes = getNodes();
+        if (nodes != null) {
+            for (Node node : nodes) {
+                if (id == (Long) node.getID()) {
+                    return node;
+                }
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Returns a copy of a list of properties for a given node
+     *
+     * (non-Javadoc)
+     * @see org.opendaylight.controller.switchmanager.ISwitchManager#getNodeProps(org.opendaylight.controller.sal.core.Node)
+     */
+    @Override
+    public Map<String, Property> getNodeProps(Node node) {
+        if (isDefaultContainer) {
+            Map<String, Property> rv = null;
+            if (this.nodeProps != null) {
+                rv = this.nodeProps.get(node);
+                if (rv != null) {
+                    /* make a copy of it */
+                    rv = new HashMap<String, Property>(rv);
+                }
+            }
+            return rv;
+        } else {
+            // get it from default container
+            ISwitchManager defaultSwitchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, GlobalConstants.DEFAULT
+                            .toString(), this);
+            return defaultSwitchManager.getNodeProps(node);
+        }
+    }
+
+    @Override
+    public Property getNodeProp(Node node, String propName) {
+        Map<String, Property> propMap = getNodeProps(node);
+        return (propMap != null) ? propMap.get(propName) : null;
+    }
+
+    @Override
+    public void setNodeProp(Node node, Property prop) {
+        /* Get a copy of the property map */
+        Map<String, Property> propMap = getNodeProps(node);
+        if (propMap == null)
+            return;
+
+        propMap.put(prop.getName(), prop);
+        this.nodeProps.put(node, propMap);
+    }
+
+    @Override
+    public Status removeNodeProp(Node node, String propName) {
+        Map<String, Property> propMap = getNodeProps(node);
+        if (propMap != null) {
+               propMap.remove(propName);
+               this.nodeProps.put(node, propMap);
+        }
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public Status removeNodeAllProps(Node node) {
+        this.nodeProps.remove(node);
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public Set<NodeConnector> getUpNodeConnectors(Node node) {
+        if (nodeConnectorProps == null)
+            return null;
+
+        Set<NodeConnector> nodeConnectorSet = new HashSet<NodeConnector>();
+        for (NodeConnector nodeConnector : nodeConnectorProps.keySet()) {
+            if (((Long) nodeConnector.getNode().getID())
+                    .longValue() != (Long) node.getID())
+                continue;
+            if (isNodeConnectorEnabled(nodeConnector))
+                nodeConnectorSet.add(nodeConnector);
+        }
+
+        return nodeConnectorSet;
+    }
+
+    @Override
+    public Set<NodeConnector> getNodeConnectors(Node node) {
+        if (nodeConnectorProps == null)
+            return null;
+
+        Set<NodeConnector> nodeConnectorSet = new HashSet<NodeConnector>();
+        for (NodeConnector nodeConnector : nodeConnectorProps.keySet()) {
+            if (((Long) nodeConnector.getNode().getID())
+                    .longValue() != (Long) node.getID())
+                continue;
+            nodeConnectorSet.add(nodeConnector);
+        }
+
+        return nodeConnectorSet;
+    }
+
+    @Override
+    public Set<NodeConnector> getPhysicalNodeConnectors(Node node) {
+        if (nodeConnectorProps == null)
+            return null;
+
+        Set<NodeConnector> nodeConnectorSet = new HashSet<NodeConnector>();
+        for (NodeConnector nodeConnector : nodeConnectorProps.keySet()) {
+            if (!nodeConnector.getNode().equals(node)
+                    || isSpecial(nodeConnector)) {
+                continue;
+            }
+            nodeConnectorSet.add(nodeConnector);
+        }
+
+        return nodeConnectorSet;
+    }
+
+    /*
+     * testing utility function which assumes we are dealing with OF Node nodeconnectors only
+     */
+    @SuppressWarnings("unused")
+    private Set<Long> getEnabledNodeConnectorIds(Node node) {
+        Set<Long> ids = new HashSet<Long>();
+        Set<NodeConnector> nodeConnectors = getUpNodeConnectors(node);
+
+        if (nodeConnectors != null) {
+            for (NodeConnector nodeConnector : nodeConnectors) {
+                ids.add((Long) nodeConnector.getID());
+            }
+        }
+
+        return ids;
+    }
+
+    @Override
+    public Map<String, Property> getNodeConnectorProps(
+            NodeConnector nodeConnector) {
+        if (isDefaultContainer) {
+            Map<String, Property> rv = null;
+            if (this.nodeConnectorProps != null) {
+                rv = this.nodeConnectorProps.get(nodeConnector);
+                if (rv != null) {
+                    rv = new HashMap<String, Property>(rv);
+                }
+            }
+            return rv;
+        } else {
+            // get it from default container
+            ISwitchManager defaultSwitchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, GlobalConstants.DEFAULT
+                            .toString(), this);
+            return defaultSwitchManager.getNodeConnectorProps(nodeConnector);
+        }
+    }
+
+    @Override
+    public Property getNodeConnectorProp(NodeConnector nodeConnector,
+            String propName) {
+        Map<String, Property> propMap = getNodeConnectorProps(nodeConnector);
+        return (propMap != null) ? propMap.get(propName) : null;
+    }
+
+    @Override
+    public List<Map<String, String>> getListNodeIdNameMap() {
+        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
+        Set<Node> nset = this.nodeProps.keySet();
+        if (nset == null) {
+            return list;
+        }
+        for (Node node : nset) {
+            Map<String, String> map = new HashMap<String, String>(2);
+            Name nodeName = (Name) getNodeProp(node, Name.NamePropName);
+            map.put("nodeId", node.toString());
+            map.put("nodeName", ((nodeName == null) || nodeName.getValue()
+                    .equals("")) ? node.toString() : nodeName.getValue());
+            list.add(map);
+        }
+        return list;
+    }
+
+    private byte[] getHardwareMAC() {
+        Enumeration<NetworkInterface> nis;
+        try {
+            nis = NetworkInterface.getNetworkInterfaces();
+        } catch (SocketException e1) {
+            e1.printStackTrace();
+            return null;
+        }
+        byte[] MAC = null;
+        for (; nis.hasMoreElements();) {
+            NetworkInterface ni = nis.nextElement();
+            try {
+                MAC = ni.getHardwareAddress();
+            } catch (SocketException e) {
+                e.printStackTrace();
+            }
+            if (MAC != null) {
+                return MAC;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public byte[] getControllerMAC() {
+        return MAC;
+    }
+
+    @Override
+    public boolean isHostRefreshEnabled() {
+        return hostRefresh;
+    }
+
+    @Override
+    public int getHostRetryCount() {
+        return hostRetryCount;
+    }
+
+    @Override
+    public NodeConnector getNodeConnector(Node node, String nodeConnectorName) {
+        if (nodeConnectorNames == null)
+            return null;
+
+        Map<String, NodeConnector> map = nodeConnectorNames.get(node);
+        if (map == null)
+            return null;
+
+        return map.get(nodeConnectorName);
+    }
+
+    /**
+     * Adds a node connector and its property if any
+     * 
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param propName                 name of {@link org.opendaylight.controller.sal.core.Property}
+     * @return success or failed reason
+     */
+    @Override
+    public Status addNodeConnectorProp(NodeConnector nodeConnector, Property prop) {
+        Map<String, Property> propMap = getNodeConnectorProps(nodeConnector);
+
+        if (propMap == null) {
+            propMap = new HashMap<String, Property>();
+        }
+
+        // Just add the nodeConnector if prop is not available (in a non-default container)
+        if (prop == null) {
+            nodeConnectorProps.put(nodeConnector, propMap);
+            return new Status(StatusCode.SUCCESS, null);
+        }
+
+        propMap.put(prop.getName(), prop);
+        nodeConnectorProps.put(nodeConnector, propMap);
+
+        if (prop.getName().equals(Name.NamePropName)) {
+            if (nodeConnectorNames != null) {
+                Node node = nodeConnector.getNode();
+                Map<String, NodeConnector> map = nodeConnectorNames.get(node);
+                if (map == null) {
+                    map = new HashMap<String, NodeConnector>();
+                }
+
+                map.put(((Name) prop).getValue(), nodeConnector);
+                nodeConnectorNames.put(node, map);
+            }
+        }
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    /**
+     * Removes one property of a node connector
+     * 
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @param propName                 name of {@link org.opendaylight.controller.sal.core.Property}
+     * @return success or failed reason
+     */
+    @Override
+    public Status removeNodeConnectorProp(NodeConnector nodeConnector, String propName) {
+        Map<String, Property> propMap = getNodeConnectorProps(nodeConnector);
+
+        if (propMap == null) {
+               /* Nothing to remove */
+            return new Status(StatusCode.SUCCESS, null);
+        }
+
+        propMap.remove(propName);
+        nodeConnectorProps.put(nodeConnector, propMap);
+
+        if (nodeConnectorNames != null) {
+            Name name = ((Name) getNodeConnectorProp(nodeConnector,
+                    Name.NamePropName));
+            if (name != null) {
+                Node node = nodeConnector.getNode();
+                Map<String, NodeConnector> map = nodeConnectorNames.get(node);
+                if (map != null) {
+                    map.remove(name.getValue());
+                    nodeConnectorNames.put(node, map);
+                }
+            }
+        }
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    /**
+     * Removes all the properties of a node connector
+     * 
+     * @param nodeConnector {@link org.opendaylight.controller.sal.core.NodeConnector}
+     * @return success or failed reason
+     */
+    @Override
+    public Status removeNodeConnectorAllProps(NodeConnector nodeConnector) {
+        if (nodeConnectorNames != null) {
+            Name name = ((Name) getNodeConnectorProp(nodeConnector,
+                    Name.NamePropName));
+            if (name != null) {
+                Node node = nodeConnector.getNode();
+                Map<String, NodeConnector> map = nodeConnectorNames.get(node);
+                if (map != null) {
+                    map.remove(name.getValue());
+                    nodeConnectorNames.put(node, map);
+                }
+            }
+        }
+        nodeConnectorProps.remove(nodeConnector);
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init(Component c) {
+        Dictionary<?, ?> props = c.getServiceProperties();
+        if (props != null) {
+            this.containerName = (String) props.get("containerName");
+            log.trace("Running containerName:" + this.containerName);
+        } else {
+            // In the Global instance case the containerName is empty
+            this.containerName = "";
+        }
+        isDefaultContainer = containerName.equals(GlobalConstants.DEFAULT
+                .toString());
+
+        startUp();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        shutDown();
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+        // OSGI console
+        registerWithOSGIConsole();
+    }
+
+    /**
+     * Function called after registered the
+     * service in OSGi service registry.
+     */
+    void started() {
+        // solicit for existing inventories
+        getInventories();
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    public void setInventoryService(IInventoryService service) {
+        log.trace("Got inventory service set request {}", service);
+        this.inventoryService = service;
+
+        // solicit for existing inventories
+        getInventories();
+    }
+
+    public void unsetInventoryService(IInventoryService service) {
+        log.trace("Got a service UNset request");
+        this.inventoryService = null;
+
+        // clear existing inventories
+        clearInventories();
+    }
+
+    public void setSwitchManagerAware(ISwitchManagerAware service) {
+        log.trace("Got inventory service set request {}", service);
+        if (this.switchManagerAware != null) {
+            this.switchManagerAware.add(service);
+        }
+
+        // bulk update for newly joined
+        switchManagerAwareNotify(service);
+    }
+
+    public void unsetSwitchManagerAware(ISwitchManagerAware service) {
+        log.trace("Got a service UNset request");
+        if (this.switchManagerAware != null) {
+            this.switchManagerAware.remove(service);
+        }
+    }
+
+    public void setInventoryListener(IInventoryListener service) {
+        log.trace("Got inventory listener set request {}", service);
+        if (this.inventoryListeners != null) {
+            this.inventoryListeners.add(service);
+        }
+
+        // bulk update for newly joined
+        bulkUpdateService(service);
+    }
+
+    public void unsetInventoryListener(IInventoryListener service) {
+        log.trace("Got a service UNset request");
+        if (this.inventoryListeners != null) {
+            this.inventoryListeners.remove(service);
+        }
+    }
+
+    public void setSpanAware(ISpanAware service) {
+        log.trace("Got SpanAware set request {}", service);
+        if (this.spanAware != null) {
+            this.spanAware.add(service);
+        }
+
+        // bulk update for newly joined
+        spanAwareNotify(service);
+    }
+
+    public void unsetSpanAware(ISpanAware service) {
+        log.trace("Got a service UNset request");
+        if (this.spanAware != null) {
+            this.spanAware.remove(service);
+        }
+    }
+
+    void setClusterContainerService(IClusterContainerServices s) {
+        log.trace("Cluster Service set");
+        this.clusterContainerService = s;
+    }
+
+    void unsetClusterContainerService(IClusterContainerServices s) {
+        if (this.clusterContainerService == s) {
+            log.trace("Cluster Service removed!");
+            this.clusterContainerService = null;
+        }
+    }
+
+    private void getInventories() {
+        if (inventoryService == null) {
+            log.trace("inventory service not avaiable");
+            return;
+        }
+
+        nodeProps = this.inventoryService.getNodeProps();
+        Set<Node> nodeSet = nodeProps.keySet();
+        if (nodeSet != null) {
+            for (Node node : nodeSet) {
+                addNode(node, null);
+            }
+        }
+
+        nodeConnectorProps = inventoryService.getNodeConnectorProps();
+    }
+
+    private void clearInventories() {
+        nodeProps.clear();
+        nodeConnectorProps.clear();
+        nodeConnectorNames.clear();
+        spanNodeConnectors.clear();
+    }
+
+    private void notifyNode(Node node, UpdateType type,
+            Map<String, Property> propMap) {
+        synchronized (inventoryListeners) {
+            for (IInventoryListener service : inventoryListeners) {
+                service.notifyNode(node, type, propMap);
+            }
+        }
+    }
+
+    private void notifyNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Map<String, Property> propMap) {
+        synchronized (inventoryListeners) {
+            for (IInventoryListener service : inventoryListeners) {
+                service.notifyNodeConnector(nodeConnector, type, propMap);
+            }
+        }
+    }
+
+    /*
+     * For those joined late, bring them up-to-date.
+     */
+    private void switchManagerAwareNotify(ISwitchManagerAware service) {
+        for (Subnet sub : subnets.values()) {
+            service.subnetNotify(sub, true);
+        }
+
+        for (Node node : getNodes()) {
+            SwitchConfig sc = getSwitchConfig(node.toString());
+            if ((sc != null) && isDefaultContainer) {
+                service.modeChangeNotify(node,
+                        (Integer.parseInt(sc.getMode()) != 0));
+            }
+        }
+    }
+
+    private void bulkUpdateService(IInventoryListener service) {
+        for (Node node : getNodes()) {
+            service.notifyNode(node, UpdateType.ADDED, null);
+        }
+
+        Map<String, Property> propMap = new HashMap<String, Property>();
+        propMap.put(State.StatePropName, new State(State.EDGE_UP));
+        for (NodeConnector nodeConnector : nodeConnectorProps.keySet()) {
+            if (isNodeConnectorEnabled(nodeConnector)) {
+                service.notifyNodeConnector(nodeConnector, UpdateType.ADDED,
+                        propMap);
+            }
+        }
+    }
+
+    private void spanAwareNotify(ISpanAware service) {
+        for (Node node : getNodes()) {
+            for (SpanConfig conf : getSpanConfigList(node)) {
+                service.spanUpdate(node, conf.getPortArrayList(), true);
+            }
+        }
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    @Override
+    public Boolean isNodeConnectorEnabled(NodeConnector nodeConnector) {
+        if (nodeConnector == null)
+            return false;
+
+        Config config = (Config) getNodeConnectorProp(nodeConnector,
+                Config.ConfigPropName);
+        State state = (State) getNodeConnectorProp(nodeConnector,
+                State.StatePropName);
+        return ((config != null) && (config.getValue() == Config.ADMIN_UP)
+                && (state != null) && (state.getValue() == State.EDGE_UP));
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---Switch Manager---\n");
+        help.append("\t pns                    - Print connected nodes\n");
+        help
+                .append("\t pncs <node id>         - Print node connectors for a given node\n");
+        help
+                .append("\t pencs <node id>        - Print enabled node connectors for a given node\n");
+        help
+                .append("\t pdm <node id>          - Print switch ports in device map\n");
+        help.append("\t snt <node id> <tier>   - Set node tier number\n");
+        help
+                .append("\t hostRefresh <on/off/?> - Enable/Disable/Query host refresh\n");
+        help.append("\t hostRetry <count>      - Set host retry count\n");
+        return help.toString();
+    }
+
+    public void _pns(CommandInterpreter ci) {
+        ci
+                .println("           Node                       Type             Name             Tier");
+        if (nodeProps == null) {
+            return;
+        }
+        Set<Node> nodeSet = nodeProps.keySet();
+        if (nodeSet == null) {
+            return;
+        }
+        for (Node node : nodeSet) {
+            Name name = ((Name) getNodeProp(node, Name.NamePropName));
+            Tier tier = ((Tier) getNodeProp(node, Tier.TierPropName));
+            String nodeName = (name == null) ? "" : name.getValue();
+            int tierNum = (tier == null) ? 0 : tier.getValue();
+            ci.println(node + "            " + node.getType()
+                    + "            " + nodeName + "            " + tierNum);
+        }
+    }
+
+    public void _pencs(CommandInterpreter ci) {
+        String st = ci.nextArgument();
+        if (st == null) {
+            ci.println("Please enter node id");
+            return;
+        }
+        Long id = Long.decode(st);
+
+        Node node = NodeCreator.createOFNode(id);
+        Set<NodeConnector> nodeConnectorSet = getUpNodeConnectors(node);
+        if (nodeConnectorSet == null) {
+            return;
+        }
+        for (NodeConnector nodeConnector : nodeConnectorSet) {
+            if (nodeConnector == null) {
+                continue;
+            }
+            ci.println(nodeConnector);
+        }
+    }
+
+    public void _pncs(CommandInterpreter ci) {
+        String st = ci.nextArgument();
+        if (st == null) {
+            ci.println("Please enter node id");
+            return;
+        }
+        Long id = Long.decode(st);
+
+        ci.println("          NodeConnector               BandWidth(Gbps)     Admin     State");
+        Node node = NodeCreator.createOFNode(id);
+        Set<NodeConnector> nodeConnectorSet = getNodeConnectors(node);
+        if (nodeConnectorSet == null) {
+            return;
+        }
+        for (NodeConnector nodeConnector : nodeConnectorSet) {
+            if (nodeConnector == null) {
+                continue;
+            }
+            Map<String, Property> propMap = getNodeConnectorProps(nodeConnector);
+            Bandwidth bw = (Bandwidth) propMap.get(Bandwidth.BandwidthPropName);
+            Config config = (Config) propMap.get(Config.ConfigPropName);
+            State state = (State) propMap.get(State.StatePropName);
+            String out = nodeConnector + "           ";
+            out += (bw != null) ? bw.getValue() / Math.pow(10, 9) : "    ";
+            out += "             ";
+            out += (config != null) ? config.getValue() : " ";
+            out += "          ";
+            out += (state != null) ? state.getValue() : " ";
+            ci.println(out);
+        }
+    }
+
+    public void _pdm(CommandInterpreter ci) {
+        String st = ci.nextArgument();
+        if (st == null) {
+            ci.println("Please enter node id");
+            return;
+        }
+        Object id = Long.decode(st);
+        Switch sw = getSwitchByNode(NodeCreator.createOFNode((Long) id));
+
+        ci
+                .println("          NodeConnector                        Name");
+        if (sw == null) {
+            return;
+        }
+        Set<NodeConnector> nodeConnectorSet = sw.getNodeConnectors();
+        String nodeConnectorName;
+        if (nodeConnectorSet != null && nodeConnectorSet.size() > 0) {
+            for (NodeConnector nodeConnector : nodeConnectorSet) {
+                Map<String, Property> propMap = getNodeConnectorProps(nodeConnector);
+                nodeConnectorName = (propMap == null) ? null : ((Name) propMap
+                        .get(Name.NamePropName)).getValue();
+                if (nodeConnectorName != null) {
+                    Node node = nodeConnector.getNode();
+                    if (!node.equals(getNode((Long) id))) {
+                        log.debug("node not match {} {}", node,
+                                getNode((Long) id));
+                    }
+                    Map<String, NodeConnector> map = nodeConnectorNames
+                            .get(node);
+                    if (map != null) {
+                        NodeConnector nc = map.get(nodeConnectorName);
+                        if (nc == null) {
+                            log.debug("no nodeConnector named {}",
+                                    nodeConnectorName);
+                        } else if (!nc.equals(nodeConnector)) {
+                            log.debug("nodeConnector not match {} {}", nc,
+                                    nodeConnector);
+                        }
+                    }
+                }
+
+                ci
+                        .println(nodeConnector
+                                + "            "
+                                + ((nodeConnectorName == null) ? ""
+                                        : nodeConnectorName) + "("
+                                + nodeConnector.getID() + ")");
+            }
+        }
+    }
+
+    public void _snt(CommandInterpreter ci) {
+        String st = ci.nextArgument();
+        if (st == null) {
+            ci.println("Please enter node id");
+            return;
+        }
+        Long id = Long.decode(st);
+
+        Node node = NodeCreator.createOFNode(id);
+
+        st = ci.nextArgument();
+        if (st == null) {
+            ci.println("Please enter tier number");
+            return;
+        }
+        Integer tid = Integer.decode(st);
+        Tier tier = new Tier(tid);
+        setNodeProp(node, tier);
+    }
+
+    public void _hostRefresh(CommandInterpreter ci) {
+        String mode = ci.nextArgument();
+        if (mode == null) {
+            ci.println("expecting on/off/?");
+            return;
+        }
+        if (mode.toLowerCase().equals("on"))
+            hostRefresh = true;
+        else if (mode.toLowerCase().equals("off"))
+            hostRefresh = false;
+        else if (mode.equals("?")) {
+            if (hostRefresh)
+                ci.println("host refresh is ON");
+            else
+                ci.println("host refresh is OFF");
+        } else
+            ci.println("expecting on/off/?");
+        return;
+    }
+
+    public void _hostRetry(CommandInterpreter ci) {
+        String retry = ci.nextArgument();
+        if (retry == null) {
+            ci.println("Please enter a valid number. Current retry count is "
+                    + hostRetryCount);
+            return;
+        }
+        try {
+            hostRetryCount = Integer.parseInt(retry);
+        } catch (Exception e) {
+            ci.println("Please enter a valid number");
+        }
+        return;
+    }
+
+    @Override
+    public byte[] getNodeMAC(Node node) {
+        if (node.getType().equals(Node.NodeIDType.OPENFLOW)) {
+            byte[] gmac = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+            long dpid = (Long) node.getID();
+
+            for (short i = 0; i < 6; i++) {
+                gmac[5 - i] = (byte) dpid;
+                dpid >>= 8;
+            }
+            return gmac;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isSpecial(NodeConnector p) {
+        if (p.equals(NodeConnectorIDType.CONTROLLER) ||
+            p.equals(NodeConnectorIDType.ALL) ||
+            p.equals(NodeConnectorIDType.SWSTACK) ||
+            p.equals(NodeConnectorIDType.HWPATH)) {
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Add span configuration to local cache and notify clients
+     */
+    private void addSpanPorts(Node node, List<NodeConnector> nodeConncetors) {
+        List<NodeConnector> ncLists = new ArrayList<NodeConnector>();
+
+        for (NodeConnector nodeConnector : nodeConncetors) {
+            if (!spanNodeConnectors.contains(nodeConnector)) {
+                ncLists.add(nodeConnector);
+            }
+        }
+
+        if (ncLists.size() > 0) {
+            spanNodeConnectors.addAll(ncLists);
+            notifySpanPortChange(node, ncLists, true);
+        }
+    }
+
+    private void addSpanPorts(Node node) {
+        for (SpanConfig conf : getSpanConfigList(node)) {
+            addSpanPorts(node, conf.getPortArrayList());
+        }
+    }
+
+    private void addSpanPort(NodeConnector nodeConncetor) {
+        List<NodeConnector> ncLists = new ArrayList<NodeConnector>();
+        ncLists.add(nodeConncetor);
+        addSpanPorts(nodeConncetor.getNode(), ncLists);
+    }
+
+    /*
+     * Remove span configuration to local cache and notify clients
+     */
+    private void removeSpanPorts(Node node, List<NodeConnector> nodeConncetors) {
+        List<NodeConnector> ncLists = new ArrayList<NodeConnector>();
+
+        for (NodeConnector nodeConnector : nodeConncetors) {
+            if (!spanNodeConnectors.contains(nodeConnector)) {
+                ncLists.add(nodeConnector);
+            }
+        }
+
+        if (ncLists.size() > 0) {
+            spanNodeConnectors.removeAll(ncLists);
+            notifySpanPortChange(node, ncLists, false);
+        }
+    }
+
+    private void removeSpanPorts(Node node) {
+        for (SpanConfig conf : getSpanConfigList(node)) {
+            addSpanPorts(node, conf.getPortArrayList());
+        }
+    }
+
+    private void removeSpanPort(NodeConnector nodeConncetor) {
+        List<NodeConnector> ncLists = new ArrayList<NodeConnector>();
+        ncLists.add(nodeConncetor);
+        removeSpanPorts(nodeConncetor.getNode(), ncLists);
+    }
+
+    private void addNodeProps(Node node, Map<String, Property> propMap) {
+        if (propMap == null) {
+            propMap = new HashMap<String, Property>();
+        }
+        nodeProps.put(node, propMap);
+    }
+
+    private void removeNodeProps(Node node) {
+        if (getUpNodeConnectors(node).size() == 0) {
+            nodeProps.remove(node);
+        }
+    }
+
+    @Override
+    public Status saveConfiguration() {
+        return saveSwitchConfig();
+    }
+
+       /**
+        * Creates a Name/Tier/Bandwidth Property object based on given property
+        * name and value. Other property types are not supported yet.
+        * 
+        * @param propName  Name of the Property
+        * @param propValue Value of the Property
+        * @return {@link org.opendaylight.controller.sal.core.Property}
+        */
+       @Override
+       public Property createProperty(String propName, String propValue) {
+               if (propName == null) {
+                       log.debug("propName is null");
+                       return null;
+               }
+               if (propValue == null) {
+                       log.debug("propValue is null");
+                       return null;
+               }
+               
+               try {
+                       if (propName.equalsIgnoreCase(Name.NamePropName)) {
+                               return new Name(propValue);
+                       } else if (propName.equalsIgnoreCase(Tier.TierPropName)) {
+                               int tier = Integer.parseInt(propValue);
+                               return new Tier(tier);
+                       } else if (propName.equalsIgnoreCase(Bandwidth.BandwidthPropName)) {
+                               long bw = Long.parseLong(propValue);
+                               return new Bandwidth(bw);
+                       } else {
+                               log.debug("Not able to create {} property", propName);
+                       }
+               } catch (Exception e) {
+                       log.debug(e.getMessage());
+               }
+
+               return null;
+    }
+}
diff --git a/opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/SubnetTest.java b/opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/SubnetTest.java
new file mode 100644 (file)
index 0000000..0b9fb9c
--- /dev/null
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+
+
+public class SubnetTest {
+       
+       @Test
+       public void testSubnet() throws Exception  {
+               InetAddress ipaddr = InetAddress.getByName("100.0.0.1");
+               Subnet subnet = new Subnet(ipaddr, (short)24, (short)5);
+               InetAddress subnetAddr = InetAddress.getByName("100.0.0.100");
+               
+               Assert.assertTrue(subnet.isFlatLayer2() == true);
+               
+               Set<NodeConnector> ncSet = new HashSet<NodeConnector>();
+               Node node = NodeCreator.createOFNode(((long)10));
+               NodeConnector nc0 = NodeConnectorCreator.createOFNodeConnector((short)20, node);
+               NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector((short)30, node);
+               NodeConnector nc2 = NodeConnectorCreator.createOFNodeConnector((short)40, node);
+               
+               ncSet.add(nc0);
+               ncSet.add(nc1);
+               ncSet.add(nc2);
+               
+               subnet.addNodeConnectors(ncSet);
+               
+               Set<NodeConnector> resultncSet = subnet.getNodeConnectors();
+               Assert.assertEquals(resultncSet, ncSet);
+               
+               subnet.setNetworkAddress(subnetAddr);
+               Assert.assertTrue(subnet.getNetworkAddress().equals(subnetAddr));
+               
+               subnet.setSubnetMaskLength((short) 16);
+               Assert.assertTrue(subnet.getSubnetMaskLength() == 16);
+               
+               subnet.setVlan((short)100);
+               Assert.assertTrue(subnet.getVlan() == 100);
+               
+               Assert.assertTrue(subnet.isFlatLayer2() == false);
+
+       }
+
+}
diff --git a/opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/SwitchTest.java b/opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/SwitchTest.java
new file mode 100644 (file)
index 0000000..c3ff4f1
--- /dev/null
@@ -0,0 +1,95 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+
+public class SwitchTest {
+
+       @Test
+       public void testSwitchCreation() {
+               
+               Node node = NodeCreator.createOFNode(((long)10));
+               NodeConnector nc0 = NodeConnectorCreator.createOFNodeConnector((short)20, node);
+               NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector((short)30, node);
+               NodeConnector nc2 = NodeConnectorCreator.createOFNodeConnector((short)40, node);
+               NodeConnector nc3 = NodeConnectorCreator.createOFNodeConnector((short)50, node);
+               NodeConnector nc4 = NodeConnectorCreator.createOFNodeConnector((short)60, node);
+               NodeConnector nc5 = NodeConnectorCreator.createOFNodeConnector((short)70, node);
+
+               Set<NodeConnector> ncSet = new HashSet<NodeConnector>();
+               ArrayList<NodeConnector> portList  = new ArrayList<NodeConnector>();
+               
+               Switch sw = new Switch(node);
+               ncSet.add(nc0);
+               ncSet.add(nc1);
+               ncSet.add(nc2);
+               sw.addNodeConnector(nc3);
+               
+               portList.add(nc4);
+               portList.add(nc5);
+
+               sw.setNodeConnectors(ncSet);
+               sw.addSpanPorts(portList);
+               
+               byte[] dlAddress = {(byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06};
+               sw.setDataLayerAddress(dlAddress);
+               
+               Node resultNode = sw.getNode();
+               Set<NodeConnector> resultncSet = sw.getNodeConnectors();
+               byte[] resultdlAddress = sw.getDataLayerAddress();              
+               ArrayList<NodeConnector> resultSpanPort = (ArrayList<NodeConnector>) sw.getSpanPorts();
+               
+               Assert.assertEquals(node, resultNode);
+               for (int i = 0; i < dlAddress.length; i++)
+                       Assert.assertEquals(dlAddress[i], resultdlAddress[i]);  
+               
+               Assert.assertTrue(ncSet.equals(resultncSet));
+               
+               for (int i = 0; i < portList.size(); i++)
+                       Assert.assertEquals(portList.get(i), resultSpanPort.get(i));
+       }
+       
+       @Test
+       public void testSwitchAddRemovePort() {
+               Node node = NodeCreator.createOFNode(((long)10));
+
+               NodeConnector nc0 = NodeConnectorCreator.createOFNodeConnector((short)20, node);
+               NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector((short)30, node);
+               NodeConnector nc4 = NodeConnectorCreator.createOFNodeConnector((short)60, node);
+               NodeConnector nc5 = NodeConnectorCreator.createOFNodeConnector((short)70, node);
+               ArrayList<NodeConnector> portList  = new ArrayList<NodeConnector>();
+               
+               portList.add(nc4);
+               portList.add(nc5);
+
+               Set<NodeConnector> ncSet = new HashSet<NodeConnector>();
+               ncSet.add(nc0);
+               ncSet.add(nc1);
+               
+               Switch sw = new Switch(node);
+               sw.setNodeConnectors(ncSet);
+               sw.removeNodeConnector(nc0);
+               Assert.assertFalse(ncSet.contains(nc0));
+               
+               sw.removeSpanPorts(portList);
+               Assert.assertTrue(sw.getSpanPorts().isEmpty());
+
+       }
+}
diff --git a/opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/internal/SwitchManagerImplTest.java b/opendaylight/switchmanager/src/test/java/org/opendaylight/controller/switchmanager/internal/SwitchManagerImplTest.java
new file mode 100644 (file)
index 0000000..8d869fc
--- /dev/null
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.switchmanager.internal;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.Latency;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+
+public class SwitchManagerImplTest {
+
+       @Test
+       public void testSwitchManagerAddRemoveSubnet() {
+               SwitchManagerImpl switchmgr = new SwitchManagerImpl();
+               switchmgr.nonClusterObjectCreate();
+               
+               ArrayList<String>portList = new ArrayList<String>();
+               portList.add("1/1");
+               portList.add("1/2");
+               portList.add("1/3");
+
+               
+               SubnetConfig subnet = new SubnetConfig("subnet", "10.0.0.254/16", portList);
+               //System.out.println("*" + switchmgr.addSubnet(subnet) + "*");
+               Status addResult = (switchmgr.addSubnet(subnet));
+               Assert.assertTrue(addResult.isSuccess());
+               
+               Status removeResult = (switchmgr.removeSubnet(subnet.getName()));
+               Assert.assertTrue(removeResult.isSuccess());
+
+               SubnetConfig subnetConfigResult = switchmgr.getSubnetConfig(subnet.getName());
+               Assert.assertTrue(subnetConfigResult == null);
+               
+       }
+       
+       @Test
+       public void testSwitchManagerNodeConnectors() {
+               SwitchManagerImpl switchmgr = new SwitchManagerImpl();
+               switchmgr.nonClusterObjectCreate();
+               
+               State state;
+               Bandwidth bw;
+               Latency l;
+               
+               NodeConnector[] headnc = new NodeConnector[5];
+               NodeConnector[] tailnc = new NodeConnector[5];
+
+               Set<Property> props = new HashSet<Property>();
+               state = new State(State.EDGE_UP);
+               bw = new Bandwidth(Bandwidth.BW100Gbps);
+               l = new Latency(Latency.LATENCY100ns);
+               props.add(state);
+               props.add(bw);
+               props.add(l);
+               
+               for (short i = 1; i < 6; i = (short)(i + 1)) {
+
+                               headnc[i - 1] = NodeConnectorCreator.createOFNodeConnector(i, NodeCreator.createOFNode((long)i));
+                               tailnc[i - 1] = NodeConnectorCreator.createOFNodeConnector((short)(i+10), NodeCreator.createOFNode((long)(i+10)));
+                               switchmgr.updateNode(headnc[i - 1].getNode(), UpdateType.ADDED, props);
+                               switchmgr.updateNode(tailnc[i - 1].getNode(), UpdateType.ADDED, props);
+
+                               switchmgr.updateNodeConnector(headnc[i - 1], UpdateType.ADDED, props);
+                               switchmgr.updateNodeConnector(tailnc[i - 1], UpdateType.ADDED, props);
+               }
+               
+               for (int i = 0; i < 5; i++) {
+                       Property bwProp = switchmgr.getNodeConnectorProp(headnc[i], Bandwidth.BandwidthPropName);
+                       Assert.assertTrue(bwProp.equals(bw));
+                       Property latencyProp = switchmgr.getNodeConnectorProp(tailnc[i], Latency.LatencyPropName);
+                       Assert.assertEquals(latencyProp, l);
+                       
+                       byte[] headNodeMac = switchmgr.getNodeMAC(headnc[i].getNode());
+                       Assert.assertTrue(headNodeMac[headNodeMac.length - 1] == (byte)(i + 1));
+               }
+               
+               Set<Node> nodes = switchmgr.getNodes();
+               for (int i = 0; i < 5; i++) {
+                       if (nodes.contains(headnc[i].getNode()) == true) 
+                                       nodes.remove(headnc[i].getNode());
+                       
+                       if (nodes.contains(tailnc[i].getNode()) == true) 
+                                       nodes.remove(tailnc[i].getNode());
+                       
+               }
+               Assert.assertTrue(nodes.isEmpty());
+       }
+       
+}
diff --git a/opendaylight/topologymanager/pom.xml b/opendaylight/topologymanager/pom.xml
new file mode 100755 (executable)
index 0000000..aebaeca
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../commons/opendaylight</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>topologymanager</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+       <groupId>org.apache.felix</groupId>
+       <artifactId>maven-bundle-plugin</artifactId>
+       <version>2.3.6</version>
+       <extensions>true</extensions>
+       <configuration>
+         <instructions>
+           <Export-Package>
+             org.opendaylight.controller.topologymanager
+           </Export-Package>
+           <Import-Package>
+             org.opendaylight.controller.sal.core,
+             org.opendaylight.controller.sal.utils,
+             org.opendaylight.controller.sal.packet,
+             org.opendaylight.controller.sal.topology,
+             org.opendaylight.controller.configuration,
+             org.opendaylight.controller.clustering.services,
+          org.osgi.service.component,
+             org.slf4j,
+          org.apache.felix.dm,
+          org.apache.commons.lang3.tuple,
+                 org.eclipse.osgi.framework.console,
+          org.osgi.framework
+           </Import-Package>
+        <Bundle-Activator>
+          org.opendaylight.controller.topologymanager.internal.Activator
+        </Bundle-Activator>
+         </instructions>
+       </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>configuration</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    
+  </dependencies>
+</project>
diff --git a/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/ITopologyManager.java b/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/ITopologyManager.java
new file mode 100644 (file)
index 0000000..73bbc4a
--- /dev/null
@@ -0,0 +1,114 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topologymanager;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * Interface class that provides methods to interact with
+ * network topology database
+ */
+public interface ITopologyManager {
+    /**
+     * Query to determine if the specified NodeConnector is connected
+     * to another Node or is a leaf for the network
+     * @param p The node connector
+     * @return true if the NodeConnector is connected to another
+     * switch false otherwise
+     */
+    public boolean isInternal(NodeConnector p);
+
+    /**
+     * Retrieves a map of all known link connections between nodes
+     * including their properties
+     * @return the map as specified
+     */
+    public Map<Edge, Set<Property>> getEdges();
+
+    /**
+     * Returns an unmodifiable map indexed by the Node and reporting
+     * all the edges getting out/in from/to the Node
+     * @return the map as specified
+     */
+    public Map<Node, Set<Edge>> getNodeEdges();
+
+    /**
+     * Add or Update an Host port in the topology manager DB
+     *
+     * @param p NodeConnector to which an host is connected
+     * @param h the Host connected to the NodeConnector
+     * @param t type of update if Add/Delete/Update
+     */
+    public void updateHostLink(NodeConnector p, Host h, UpdateType t,
+            Set<Property> props);
+
+    /**
+     * Return a set of NodeConnector that have hosts attached to them
+     *
+     * @return A set with all the NodeConnectors known to have an host
+     * attached
+     */
+    public Set<NodeConnector> getNodeConnectorWithHost();
+
+    /**
+     * Return the Host attached to a NodeConnector with Host
+     *
+     * @return The Host attached to a NodeConnector
+     */
+    public Host getHostAttachedToNodeConnector(NodeConnector p);
+
+    /**
+     * Returns a copy map which associates every node with all the
+     * NodeConnectors of the node that have an Host attached to it
+     *
+     * @return A map of all the Nodes with NodeConnectors
+     */
+    public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost();
+
+    /**
+     * Adds user configured link
+     *
+     * @param link User configured link
+     * @return "Success" or error reason
+     */
+    public Status addUserLink(TopologyUserLinkConfig link);
+
+    /**
+     * Deletes user configured link
+     *
+     * @param linkName The name of the user configured link
+     * @return "Success" or error reason
+     */
+    public Status deleteUserLink(String linkName);
+
+    /**
+     * Saves user configured links into config file
+     *
+     * @return "Success" or error reason
+     */
+    public Status saveConfig();
+
+    /**
+     * Gets all the user configured links
+     *
+     * @return The map of the user configured links
+     */
+    public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks();
+}
diff --git a/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/ITopologyManagerAware.java b/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/ITopologyManagerAware.java
new file mode 100644 (file)
index 0000000..a79da60
--- /dev/null
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topologymanager;
+
+import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
+
+/**
+ * Interface for all the listener of topology updates created by
+ * Topology Manager, effectively speaking the updates are an extension
+ * of the IListenTopoUpdates coming from SAL.
+ */
+public interface ITopologyManagerAware extends IListenTopoUpdates {
+}
diff --git a/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/TopologyUserLinkConfig.java b/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/TopologyUserLinkConfig.java
new file mode 100644 (file)
index 0000000..1bd9ac6
--- /dev/null
@@ -0,0 +1,255 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topologymanager;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opendaylight.controller.sal.utils.GUIField;
+
+/**
+ * Interface class that provides methods to manipulate user configured link
+ */
+public class TopologyUserLinkConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final String regexDatapathID = "^([0-9a-fA-F]{1,2}[:-]){7}[0-9a-fA-F]{1,2}$";
+    private static final String regexDatapathIDLong = "^[0-9a-fA-F]{1,16}$";
+    private static final String guiFields[] = { GUIField.STATUS.toString(),
+            GUIField.NAME.toString(), GUIField.SRCNODE.toString(),
+            GUIField.SRCPORT.toString(), GUIField.DSTNODE.toString(),
+            GUIField.DSTPORT.toString() };
+
+    public enum STATUS {
+        SUCCESS("Success"), LINKDOWN("Link Down"), INCORRECT(
+                "Incorrect Connection");
+        private STATUS(String name) {
+            this.name = name;
+        }
+
+        private String name;
+
+        public String toString() {
+            return name;
+        }
+
+        public static STATUS fromString(String str) {
+            if (str == null)
+                return LINKDOWN;
+            if (str.equals(SUCCESS.toString()))
+                return SUCCESS;
+            if (str.equals(LINKDOWN.toString()))
+                return LINKDOWN;
+            if (str.equals(INCORRECT.toString()))
+                return INCORRECT;
+            return LINKDOWN;
+        }
+    }
+
+    private String status;
+    private String name;
+    private String srcSwitchId;
+    private String srcPort;
+    private String dstSwitchId;
+    private String dstPort;
+
+    public TopologyUserLinkConfig() {
+        super();
+        status = STATUS.LINKDOWN.toString();
+    }
+
+    public TopologyUserLinkConfig(String name, String srcSwitchId,
+            String srcPort, String dstSwitchId, String dstPort) {
+        super();
+        this.name = name;
+        this.srcSwitchId = srcSwitchId;
+        this.dstSwitchId = dstSwitchId;
+        this.srcPort = srcPort;
+        this.dstPort = dstPort;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSrcSwitchId() {
+        return srcSwitchId;
+    }
+
+    public long getSrcSwitchIDLong() {
+        return getSwitchIDLong(srcSwitchId);
+    }
+
+    public void setSrcSwitchId(String srcSwitchId) {
+        this.srcSwitchId = srcSwitchId;
+    }
+
+    public String getDstSwitchId() {
+        return dstSwitchId;
+    }
+
+    public long getDstSwitchIDLong() {
+        return getSwitchIDLong(dstSwitchId);
+    }
+
+    public void setDstSwitchId(String dstSwitchId) {
+        this.dstSwitchId = dstSwitchId;
+    }
+
+    public String getSrcPort() {
+        return srcPort;
+    }
+
+    public void setSrcPort(String srcPort) {
+        this.srcPort = srcPort;
+    }
+
+    public String getDstPort() {
+        return dstPort;
+    }
+
+    public void setDstPort(String dstPort) {
+        this.dstPort = dstPort;
+    }
+
+    public STATUS getStatus() {
+        return STATUS.fromString(status);
+    }
+
+    public void setStatus(STATUS s) {
+        this.status = s.toString();
+    }
+
+    private boolean isValidSwitchId(String switchId) {
+        return (switchId != null && (switchId.matches(regexDatapathID) || switchId
+                .matches(regexDatapathIDLong)));
+    }
+
+    private long getSwitchIDLong(String switchId) {
+        int radix = 16;
+        String switchString = "0";
+
+        if (isValidSwitchId(switchId)) {
+            if (switchId.contains(":")) {
+                // Handle the 00:00:AA:BB:CC:DD:EE:FF notation
+                switchString = switchId.replace(":", "");
+            } else if (switchId.contains("-")) {
+                // Handle the 00-00-AA-BB-CC-DD-EE-FF notation
+                switchString = switchId.replace("-", "");
+            } else {
+                // Handle the 0123456789ABCDEF notation
+                switchString = switchId;
+            }
+        }
+        return Long.parseLong(switchString, radix);
+    }
+
+    public boolean isValid() {
+        if (name == null || srcSwitchId == null || dstSwitchId == null
+                || srcPort == null || dstPort == null)
+            return false;
+        if (!isValidSwitchId(srcSwitchId) || !isValidSwitchId(dstSwitchId))
+            return false;
+        return true;
+    }
+
+    public boolean isSrcPortByName() {
+        try {
+            Short.parseShort(srcPort);
+        } catch (Exception e) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isDstPortByName() {
+        try {
+            Short.parseShort(dstPort);
+        } catch (Exception e) {
+            return true;
+        }
+        return false;
+    }
+
+    public static List<String> getGuiFieldsNames() {
+        List<String> fieldList = new ArrayList<String>();
+        for (String str : guiFields) {
+            fieldList.add(str);
+        }
+        return fieldList;
+    }
+
+    @Override
+    public String toString() {
+        return "ITopologyUserLinkConfig [status=" + status + ", name=" + name
+                + ", srcSwitchId=" + srcSwitchId + ", srcPort=" + srcPort
+                + ", dstSwitchId=" + dstSwitchId + ", dstPort=" + dstPort + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((dstPort == null) ? 0 : dstPort.hashCode());
+        result = prime * result
+                + ((dstSwitchId == null) ? 0 : dstSwitchId.hashCode());
+        result = prime * result + ((srcPort == null) ? 0 : srcPort.hashCode());
+        result = prime * result
+                + ((srcSwitchId == null) ? 0 : srcSwitchId.hashCode());
+        return result;
+    }
+
+    public boolean equals(Long srcNid, String srcPortName, Long dstNid,
+            String dstPortName) {
+        if (srcNid.equals(getSrcSwitchIDLong())
+                && dstNid.equals(getDstSwitchIDLong())
+                && srcPortName.equals(getSrcPort())
+                && dstPortName.equals(getDstPort())) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TopologyUserLinkConfig other = (TopologyUserLinkConfig) obj;
+        if (dstPort == null) {
+            if (other.dstPort != null)
+                return false;
+        } else if (!dstPort.equals(other.dstPort))
+            return false;
+        if (dstSwitchId == null) {
+            if (other.dstSwitchId != null)
+                return false;
+        } else if (!dstSwitchId.equals(other.dstSwitchId))
+            return false;
+        if (srcPort == null) {
+            if (other.srcPort != null)
+                return false;
+        } else if (!srcPort.equals(other.srcPort))
+            return false;
+        if (srcSwitchId == null) {
+            if (other.srcSwitchId != null)
+                return false;
+        } else if (!srcSwitchId.equals(other.srcSwitchId))
+            return false;
+        return true;
+    }
+}
diff --git a/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/internal/Activator.java b/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/internal/Activator.java
new file mode 100644 (file)
index 0000000..d0d9bfa
--- /dev/null
@@ -0,0 +1,98 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topologymanager.internal;
+
+import org.apache.felix.dm.Component;
+
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
+import org.opendaylight.controller.sal.topology.ITopologyService;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        Object[] res = { TopologyManagerImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(TopologyManagerImpl.class)) {
+            // export the service needed to listen to topology updates
+            c.setInterface(new String[] { IListenTopoUpdates.class.getName(),
+                    ITopologyManager.class.getName(),
+                    IConfigurationContainerAware.class.getName() }, null);
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ITopologyService.class).setCallbacks("setTopoService",
+                    "unsetTopoService").setRequired(true));
+
+            // These are all the listeners for a topology manager
+            // updates, there could be many or none
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ITopologyManagerAware.class).setCallbacks(
+                    "setTopologyManagerAware", "unsetTopologyManagerAware")
+                    .setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IClusterContainerServices.class).setCallbacks(
+                    "setClusterContainerService",
+                    "unsetClusterContainerService").setRequired(true));
+        }
+    }
+}
diff --git a/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/internal/TopologyManagerImpl.java b/opendaylight/topologymanager/src/main/java/org/opendaylight/controller/topologymanager/internal/TopologyManagerImpl.java
new file mode 100644 (file)
index 0000000..92d0c39
--- /dev/null
@@ -0,0 +1,816 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topologymanager.internal;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.felix.dm.Component;
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.configuration.IConfigurationContainerAware;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.TimeStamp;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
+import org.opendaylight.controller.sal.topology.ITopologyService;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.IObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectWriter;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+import org.opendaylight.controller.topologymanager.TopologyUserLinkConfig;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The class describes TopologyManager which is the central repository of the
+ * network topology. It provides service for applications to interact with
+ * topology database and notifies all the listeners of topology changes.
+ */
+public class TopologyManagerImpl implements ITopologyManager,
+        IConfigurationContainerAware, IListenTopoUpdates, IObjectReader,
+        CommandProvider {
+    private static final Logger log = LoggerFactory
+            .getLogger(TopologyManagerImpl.class);
+    private ITopologyService topoService = null;
+    private IClusterContainerServices clusterContainerService = null;
+    // DB of all the Edges with properties which constitute our topology
+    private ConcurrentMap<Edge, Set<Property>> edgesDB = null;
+    // DB of all NodeConnector which are part of Edges, meaning they
+    // are connected to another NodeConnector on the other side
+    private ConcurrentMap<NodeConnector, Set<Property>> nodeConnectorsDB = null;
+    // DB of all the NodeConnectors with an Host attached to it
+    private ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>> hostsDB = null;
+    // Topology Manager Aware listeners
+    private Set<ITopologyManagerAware> topologyManagerAware = Collections
+            .synchronizedSet(new HashSet<ITopologyManagerAware>());
+
+    private static String ROOT = GlobalConstants.STARTUPHOME.toString();
+    private String userLinksFileName = null;
+    private ConcurrentMap<String, TopologyUserLinkConfig> userLinks;
+    
+    void nonClusterObjectCreate() {
+       edgesDB = new ConcurrentHashMap<Edge, Set<Property>>();
+       hostsDB = new ConcurrentHashMap<NodeConnector, ImmutablePair<Host, Set<Property>>>();
+       userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
+       nodeConnectorsDB = new ConcurrentHashMap<NodeConnector, Set<Property>>();
+    }
+    
+
+    void setTopologyManagerAware(ITopologyManagerAware s) {
+        if (this.topologyManagerAware != null) {
+            this.topologyManagerAware.add(s);
+        }
+    }
+
+    void unsetTopologyManagerAware(ITopologyManagerAware s) {
+        if (this.topologyManagerAware != null) {
+            this.topologyManagerAware.remove(s);
+        }
+    }
+
+    void setTopoService(ITopologyService s) {
+        this.topoService = s;
+    }
+
+    void unsetTopoService(ITopologyService s) {
+        if (this.topoService == s) {
+            this.topoService = null;
+        }
+    }
+
+    void setClusterContainerService(IClusterContainerServices s) {
+        log.debug("Cluster Service set");
+        this.clusterContainerService = s;
+    }
+
+    void unsetClusterContainerService(IClusterContainerServices s) {
+        if (this.clusterContainerService == s) {
+            log.debug("Cluster Service removed!");
+            this.clusterContainerService = null;
+        }
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init(Component c) {
+        String containerName = null;
+        Dictionary props = c.getServiceProperties();
+        if (props != null) {
+            containerName = (String) props.get("containerName");
+        } else {
+            // In the Global instance case the containerName is empty
+            containerName = "UNKNOWN";
+        }
+
+        if (this.clusterContainerService == null) {
+            log.error("Cluster Services is null, not expected!");
+            return;
+        }
+
+        if (this.topoService == null) {
+            log.error("Topology Services is null, not expected!");
+            return;
+        }
+
+        try {
+            this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService
+                    .createCache("topologymanager.edgesDB", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheExistException cee) {
+            log.error("topologymanager.edgesDB Cache already exists - "
+                    + "destroy and recreate if needed");
+        } catch (CacheConfigException cce) {
+            log.error("topologymanager.edgesDB Cache configuration invalid - "
+                    + "check cache mode");
+        }
+
+        try {
+            this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
+                    .createCache("topologymanager.hostsDB", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheExistException cee) {
+            log.error("topologymanager.hostsDB Cache already exists - "
+                    + "destroy and recreate if needed");
+        } catch (CacheConfigException cce) {
+            log.error("topologymanager.hostsDB Cache configuration invalid - "
+                    + "check cache mode");
+        }
+
+        try {
+            this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
+                    .createCache("topologymanager.nodeConnectorDB", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheExistException cee) {
+            log.error("topologymanager.nodeConnectorDB Cache already exists"
+                    + " - destroy and recreate if needed");
+        } catch (CacheConfigException cce) {
+            log.error("topologymanager.nodeConnectorDB Cache configuration "
+                    + "invalid - check cache mode");
+        }
+
+        userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
+
+        userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
+        registerWithOSGIConsole();
+        loadConfiguration();
+    }
+
+    /**
+     * Function called after the topology manager has registered the
+     * service in OSGi service registry.
+     *
+     */
+    void started() {
+        // SollicitRefresh MUST be called here else if called at init
+        // time it may sollicit refresh too soon.
+        log.debug("Sollicit topology refresh");
+        topoService.sollicitRefresh();
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+        if (this.clusterContainerService == null) {
+            log.error("Cluster Services is null, not expected!");
+            this.edgesDB = null;
+            this.hostsDB = null;
+            this.nodeConnectorsDB = null;
+            return;
+        }
+        this.clusterContainerService.destroyCache("topologymanager.edgesDB");
+        this.edgesDB = null;
+        this.clusterContainerService.destroyCache("topologymanager.hostsDB");
+        this.hostsDB = null;
+        this.clusterContainerService
+                .destroyCache("topologymanager.nodeConnectorDB");
+        this.nodeConnectorsDB = null;
+        log.debug("Topology Manager DB DE-allocated");
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadConfiguration() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<String, TopologyUserLinkConfig> confList = (ConcurrentMap<String, TopologyUserLinkConfig>) objReader
+                .read(this, userLinksFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (TopologyUserLinkConfig conf : confList.values()) {
+            addUserLink(conf);
+        }
+    }
+
+    @Override
+    public Status saveConfig() {
+        // Publish the save config event to the cluster nodes
+        /**
+         * Get the CLUSTERING SERVICES WORKING BEFORE TRYING THIS
+
+        configSaveEvent.put(new Date().getTime(), SAVE);
+         */
+        return saveConfigInternal();
+    }
+
+    public Status saveConfigInternal() {
+       Status retS;
+        ObjectWriter objWriter = new ObjectWriter();
+
+        retS = objWriter.write(
+                new ConcurrentHashMap<String, TopologyUserLinkConfig>(
+                        userLinks), userLinksFileName);
+
+        if (retS.isSuccess()) {
+            return retS;
+        } else {
+            return new Status(StatusCode.INTERNALERROR, "Save failed");
+        }
+    }
+
+    @Override
+    public Map<Node, Set<Edge>> getNodeEdges() {
+        if (this.edgesDB == null) {
+            return null;
+        }
+
+        HashMap<Node, Set<Edge>> res = new HashMap<Node, Set<Edge>>();
+        for (Edge key : this.edgesDB.keySet()) {
+            // Lets analyze the tail
+            Node node = key.getTailNodeConnector().getNode();
+            Set<Edge> nodeEdges = res.get(node);
+            if (nodeEdges == null) {
+                nodeEdges = new HashSet<Edge>();
+            }
+            nodeEdges.add(key);
+            // We need to re-add to the MAP even if the element was
+            // already there so in case of clustered services the map
+            // gets updated in the cluster
+            res.put(node, nodeEdges);
+
+            // Lets analyze the head
+            node = key.getHeadNodeConnector().getNode();
+            nodeEdges = res.get(node);
+            if (nodeEdges == null) {
+                nodeEdges = new HashSet<Edge>();
+            }
+            nodeEdges.add(key);
+            // We need to re-add to the MAP even if the element was
+            // already there so in case of clustered services the map
+            // gets updated in the cluster
+            res.put(node, nodeEdges);
+        }
+
+        return res;
+    }
+
+    @Override
+    public boolean isInternal(NodeConnector p) {
+        if (this.nodeConnectorsDB == null) {
+            return false;
+        }
+
+        // This is an internal NodeConnector if is connected to
+        // another Node i.e it's part of the nodeConnectorsDB
+        return (this.nodeConnectorsDB.get(p) != null);
+    }
+
+    /**
+     * The Map returned is a copy of the current topology hence if the
+     * topology changes the copy doesn't
+     *
+     * @return A Map representing the current topology expressed as
+     * edges of the network
+     */
+    @Override
+    public Map<Edge, Set<Property>> getEdges() {
+        if (this.edgesDB == null) {
+            return null;
+        }
+
+        HashMap<Edge, Set<Property>> res = new HashMap<Edge, Set<Property>>();
+        for (Edge key : this.edgesDB.keySet()) {
+            // Sets of props are copied because the composition of
+            // those properties could change with time
+            HashSet<Property> prop = new HashSet<Property>(this.edgesDB
+                    .get(key));
+            // We can simply reuse the key because the object is
+            // immutable so doesn't really matter that we are
+            // referencing the only owned by a different table, the
+            // meaning is the same because doesn't change with time.
+            res.put(key, prop);
+        }
+
+        return res;
+    }
+
+    // TODO remove with spring-dm removal
+    /**
+     * @param set the topologyAware to set
+     */
+    public void setTopologyAware(Set<Object> set) {
+        for (Object s : set) {
+            setTopologyManagerAware((ITopologyManagerAware) s);
+        }
+    }
+
+    @Override
+    public Set<NodeConnector> getNodeConnectorWithHost() {
+        if (this.hostsDB == null) {
+            return null;
+        }
+
+        return (this.hostsDB.keySet());
+    }
+
+    @Override
+    public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
+        if (this.hostsDB == null) {
+            return null;
+        }
+        HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
+
+        for (NodeConnector p : this.hostsDB.keySet()) {
+            Node n = p.getNode();
+            Set<NodeConnector> pSet = res.get(n);
+            if (pSet == null) {
+                // Create the HashSet if null
+                pSet = new HashSet<NodeConnector>();
+                res.put(n, pSet);
+            }
+
+            // Keep updating the HashSet, given this is not a
+            // clustered map we can just update the set without
+            // worrying to update the hashmap.
+            pSet.add(p);
+        }
+
+        return (res);
+    }
+
+    @Override
+    public Host getHostAttachedToNodeConnector(NodeConnector p) {
+        if (this.hostsDB == null) {
+            return null;
+        }
+
+        return (this.hostsDB.get(p).getLeft());
+    }
+
+    @Override
+    public void updateHostLink(NodeConnector p, Host h, UpdateType t,
+            Set<Property> props) {
+        if (this.hostsDB == null) {
+            return;
+        }
+
+        switch (t) {
+        case ADDED:
+        case CHANGED:
+            // Clone the property set in case non null else just
+            // create an empty one. Caches allocated via infinispan
+            // don't allow null values
+            if (props == null) {
+                props = new HashSet<Property>();
+            } else {
+                props = new HashSet<Property>(props);
+            }
+
+            this.hostsDB.put(p, new ImmutablePair(h, props));
+            break;
+        case REMOVED:
+            this.hostsDB.remove(p);
+            break;
+        }
+    }
+
+    @Override
+    public void edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
+        switch (type) {
+        case ADDED:
+            // Make sure the props are non-null
+            if (props == null) {
+                props = (Set<Property>) new HashSet();
+            } else {
+                // Copy the set so noone is going to change the content
+                props = (Set<Property>) new HashSet(props);
+            }
+
+            // Now make sure thre is the creation timestamp for the
+            // edge, if not there timestamp with the first update
+            boolean found_create = false;
+            for (Property prop : props) {
+                if (prop instanceof TimeStamp) {
+                    TimeStamp t = (TimeStamp) prop;
+                    if (t.getTimeStampName().equals("creation")) {
+                        found_create = true;
+                    }
+                }
+            }
+
+            if (!found_create) {
+                TimeStamp t = new TimeStamp(System.currentTimeMillis(),
+                        "creation");
+                props.add(t);
+            }
+
+            // Now add this in the database eventually overriding
+            // something that may have been already existing
+            this.edgesDB.put(e, props);
+
+            // Now populate the DB of NodeConnectors
+            // NOTE WELL: properties are empy sets, not really needed
+            // for now.
+            this.nodeConnectorsDB.put(e.getHeadNodeConnector(),
+                    new HashSet<Property>());
+            this.nodeConnectorsDB.put(e.getTailNodeConnector(),
+                    new HashSet<Property>());
+            break;
+        case REMOVED:
+            // Now remove the edge from edgesDB
+            this.edgesDB.remove(e);
+
+            // Now lets update the NodeConnectors DB, the assumption
+            // here is that two NodeConnector are exclusively
+            // connected by 1 and only 1 edge, this is reasonable in
+            // the same plug (virtual of phisical) we can assume two
+            // cables won't be plugged. This could break only in case
+            // of devices in the middle that acts as hubs, but it
+            // should be safe to assume that won't happen.
+            this.nodeConnectorsDB.remove(e.getHeadNodeConnector());
+            this.nodeConnectorsDB.remove(e.getTailNodeConnector());
+            break;
+        case CHANGED:
+            Set<Property> old_props = this.edgesDB.get(e);
+
+            // When property changes lets make sure we can change it
+            // all except the creation time stamp because that should
+            // be changed only when the edge is destroyed and created
+            // again
+            TimeStamp tc = null;
+            for (Property prop : old_props) {
+                if (prop instanceof TimeStamp) {
+                    TimeStamp t = (TimeStamp) prop;
+                    if (t.getTimeStampName().equals("creation")) {
+                        tc = t;
+                    }
+                }
+            }
+
+            // Now lest make sure new properties are non-null
+            // Make sure the props are non-null
+            if (props == null) {
+                props = (Set<Property>) new HashSet();
+            } else {
+                // Copy the set so noone is going to change the content
+                props = (Set<Property>) new HashSet(props);
+            }
+
+            // Now lets remove the creation property if exist in the
+            // new props
+            for (Iterator<Property> i = props.iterator(); i.hasNext();) {
+                Property prop = i.next();
+                if (prop instanceof TimeStamp) {
+                    TimeStamp t = (TimeStamp) prop;
+                    if (t.getTimeStampName().equals("creation")) {
+                        i.remove();
+                    }
+                }
+            }
+
+            // Now lets add the creation timestamp in it
+            if (tc != null) {
+                props.add(tc);
+            }
+
+            // Finally update
+            this.edgesDB.put(e, props);
+            break;
+        }
+
+        // Now update the listeners
+        for (ITopologyManagerAware s : this.topologyManagerAware) {
+            try {
+                s.edgeUpdate(e, type, props);
+            } catch (Exception exc) {
+                log.error("Exception on callback", exc);
+            }
+        }
+    }
+
+    private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
+        TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(link
+                .getName(), link.getDstSwitchId(), link.getDstPort(), link
+                .getSrcSwitchId(), link.getSrcPort());
+        return getLinkTuple(rLink);
+    }
+
+    private Edge getLinkTuple(TopologyUserLinkConfig link) {
+        Edge linkTuple = null;
+        Long sID = link.getSrcSwitchIDLong();
+        Long dID = link.getDstSwitchIDLong();
+        Short srcPort = Short.valueOf((short) 0);
+        Short dstPort = Short.valueOf((short) 0);
+        if (link.isSrcPortByName()) {
+            // TODO find the inventory service to do this, for now 0
+            //srcPort = srcSw.getPortNumber(link.getSrcPort());
+        } else {
+            srcPort = Short.parseShort(link.getSrcPort());
+        }
+
+        if (link.isDstPortByName()) {
+            //dstPort = dstSw.getPortNumber(link.getDstPort());;
+        } else {
+            dstPort = Short.parseShort(link.getDstPort());
+        }
+
+        // if atleast 1 link exists for the srcPort and atleast 1 link exists for the dstPort
+        // that makes it ineligible for the Manual link addition
+        // This is just an extra protection to avoid mis-programming.
+        boolean srcLinkExists = false;
+        boolean dstLinkExists = false;
+        /**
+         * Disabling this optimization for now to understand the real benefit of doing this.
+         * Since this is a Manual Link addition, the user knows what he is doing and it is
+         * not good to restrict such creativity...
+         */
+        /*
+          Set <Edge> links = oneTopology.getLinks().keySet();
+          if (links != null) {
+          for (Edge eLink : links) {
+          if (!eLink.isUserCreated() &&
+          eLink.getSrc().getSid().equals(link.getSrcSwitchIDLong()) &&
+          eLink.getSrc().getPort().equals(srcPort)) {
+          srcLinkExists = true;
+          }
+
+          if (!eLink.isUserCreated() &&
+          eLink.getSrc().getSid().equals(link.getSrcSwitchIDLong()) &&
+          eLink.getSrc().getPort().equals(srcPort)) {
+          dstLinkExists = true;
+          }
+
+          if (!eLink.isUserCreated() &&
+          eLink.getDst().getSid().equals(link.getSrcSwitchIDLong()) &&
+          eLink.getDst().getPort().equals(srcPort)) {
+          srcLinkExists = true;
+          }
+
+          if (!eLink.isUserCreated() &&
+          eLink.getDst().getSid().equals(link.getSrcSwitchIDLong()) &&
+          eLink.getDst().getPort().equals(srcPort)) {
+          dstLinkExists = true;
+          }
+          }
+          }
+         */
+        //TODO check a way to validate the port with inventory services
+        //if (srcSw.getPorts().contains(srcPort) &&
+        //dstSw.getPorts().contains(srcPort) &&
+        if (!srcLinkExists && !dstLinkExists) {
+            Node sNode = null;
+            Node dNode = null;
+            NodeConnector sPort = null;
+            NodeConnector dPort = null;
+            linkTuple = null;
+            try {
+                sNode = new Node(Node.NodeIDType.OPENFLOW, sID);
+                dNode = new Node(Node.NodeIDType.OPENFLOW, dID);
+                sPort = new NodeConnector(
+                        NodeConnector.NodeConnectorIDType.OPENFLOW, srcPort,
+                        sNode);
+                dPort = new NodeConnector(
+                        NodeConnector.NodeConnectorIDType.OPENFLOW, dstPort,
+                        dNode);
+                linkTuple = new Edge(sPort, dPort);
+            } catch (ConstructionException cex) {
+            }
+            return linkTuple;
+        }
+
+        if (srcLinkExists && dstLinkExists) {
+            link.setStatus(TopologyUserLinkConfig.STATUS.INCORRECT);
+        }
+        return null;
+    }
+
+    @Override
+    public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
+        return userLinks;
+    }
+
+    @Override
+    public Status addUserLink(TopologyUserLinkConfig link) {
+        if (!link.isValid()) {
+            return new Status(StatusCode.BADREQUEST, 
+                       "Configuration Invalid. Please check the parameters");
+        }
+        if (userLinks.get(link.getName()) != null) {
+            return new Status(StatusCode.CONFLICT, 
+                       "Link with name : " + link.getName()
+                    + " already exists. Please use another name");
+        }
+        if (userLinks.containsValue(link)) {
+            return new Status(StatusCode.CONFLICT, "Link configuration exists");
+        }
+
+        link.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
+        userLinks.put(link.getName(), link);
+
+        Edge linkTuple = getLinkTuple(link);
+        if (linkTuple != null) {
+            try {
+                // TODO The onetopology will be gone too, topology
+                //manager is the master of the topology at this point
+                //if (oneTopology.addUserConfiguredLink(linkTuple)) {
+                linkTuple = getReverseLinkTuple(link);
+                //if (oneTopology.addUserConfiguredLink(linkTuple)) {
+                link.setStatus(TopologyUserLinkConfig.STATUS.SUCCESS);
+                //}
+                //}
+            } catch (Exception e) {
+                return new Status(StatusCode.INTERNALERROR,
+                               "Exception while adding custom link : " + 
+                                               e.getMessage());
+            }
+        }
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public Status deleteUserLink(String linkName) {
+        if (linkName == null) {
+            return new Status(StatusCode.BADREQUEST, 
+                       "A valid linkName is required to Delete a link");
+        }
+
+        TopologyUserLinkConfig link = userLinks.get(linkName);
+
+        Edge linkTuple = getLinkTuple(link);
+        userLinks.remove(linkName);
+        if (linkTuple != null) {
+            try {
+                //oneTopology.deleteUserConfiguredLink(linkTuple);
+            } catch (Exception e) {
+                log
+                        .warn("Harmless : Exception while Deleting User Configured link "
+                                + link + " " + e.toString());
+            }
+            linkTuple = getReverseLinkTuple(link);
+            try {
+                //oneTopology.deleteUserConfiguredLink(linkTuple);
+            } catch (Exception e) {
+                log
+                        .error("Harmless : Exception while Deleting User Configured Reverse link "
+                                + link + " " + e.toString());
+            }
+        }
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private void registerWithOSGIConsole() {
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        help.append("---Topology Manager---\n");
+        help
+                .append("\t addTopo name <src-sw-id> <port-number> <dst-sw-id> <port-number>\n");
+        help.append("\t delTopo name\n");
+        help.append("\t _printTopo\n");
+        return help.toString();
+    }
+
+    public void _printTopo(CommandInterpreter ci) {
+        for (String name : this.userLinks.keySet()) {
+            ci.println(name + " : " + userLinks.get(name));
+        }
+    }
+
+    public void _addTopo(CommandInterpreter ci) {
+        String name = ci.nextArgument();
+        if ((name == null)) {
+            ci.println("Please enter a valid Name");
+            return;
+        }
+
+        String dpid = ci.nextArgument();
+        if (dpid == null) {
+            ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
+            return;
+        }
+        try {
+            HexEncode.stringToLong(dpid);
+        } catch (Exception e) {
+            ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
+            return;
+        }
+
+        String port = ci.nextArgument();
+        if (port == null) {
+            ci.println("Invalid port number");
+            return;
+        }
+
+        String ddpid = ci.nextArgument();
+        if (ddpid == null) {
+            ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
+            return;
+        }
+        try {
+            HexEncode.stringToLong(ddpid);
+        } catch (Exception e) {
+            ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
+            return;
+        }
+
+        String dport = ci.nextArgument();
+        if (dport == null) {
+            ci.println("Invalid port number");
+            return;
+        }
+        TopologyUserLinkConfig config = new TopologyUserLinkConfig(name,
+                dpid, port, ddpid, dport);
+        ci.println(this.addUserLink(config));
+    }
+
+    public void _delTopo(CommandInterpreter ci) {
+        String name = ci.nextArgument();
+        if ((name == null)) {
+            ci.println("Please enter a valid Name");
+            return;
+        }
+        this.deleteUserLink(name);
+    }
+
+    @Override
+    public Object readObject(ObjectInputStream ois)
+            throws FileNotFoundException, IOException, ClassNotFoundException {
+        // TODO Auto-generated method stub
+        return ois.readObject();
+    }
+
+    @Override
+    public Status saveConfiguration() {
+        return saveConfig();
+    }
+
+    @Override
+    public void edgeOverUtilized(Edge edge) {
+        log.warn("Link Utilization above normal: " + edge);
+    }
+
+    @Override
+    public void edgeUtilBackToNormal(Edge edge) {
+        log.warn("Link Utilization back to normal: " + edge);
+    }
+
+}
diff --git a/opendaylight/topologymanager/src/test/java/org/opendaylight/controller/topologymanager/internal/TopologyManagerImplTest.java b/opendaylight/topologymanager/src/test/java/org/opendaylight/controller/topologymanager/internal/TopologyManagerImplTest.java
new file mode 100644 (file)
index 0000000..f2cf236
--- /dev/null
@@ -0,0 +1,367 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topologymanager.internal;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Latency;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Node.NodeIDType;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.State;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.topologymanager.TopologyUserLinkConfig;
+
+public class TopologyManagerImplTest { 
+       
+       /*
+        * Sets the node, edges and properties for edges here:
+        * Edge <SwitchId : NodeConnectorId> :
+        * <1:1>--><11:11>; <1:2>--><11:12>; 
+        * <3:3>--><13:13>; <3:4>--><13:14>;
+        * <5:5>--><15:15>; <5:6>--><15:16>;
+        * Method used by two tests: testGetNodeEdges and testGetEdges
+        * @param topoManagerImpl
+        * @throws ConstructionException
+        */
+       public void setNodeEdges(TopologyManagerImpl topoManagerImpl) throws ConstructionException {
+               topoManagerImpl.nonClusterObjectCreate();
+               
+               State state;
+               Bandwidth bw;
+               Latency l;
+               
+               Set<Property> props = new HashSet<Property>();
+               state = new State(State.EDGE_UP);
+               bw = new Bandwidth(Bandwidth.BW100Gbps);
+               l = new Latency(Latency.LATENCY100ns);
+               props.add(state);
+               props.add(bw);
+               props.add(l);
+               
+               for (short i = 1; i < 6; i=(short) (i+2)) {
+                               NodeConnector headnc1 = NodeConnectorCreator.createOFNodeConnector(i, NodeCreator.createOFNode((long)i));
+                               NodeConnector tailnc1 = NodeConnectorCreator.createOFNodeConnector((short)(i+10), NodeCreator.createOFNode((long)(i+10)));
+                               Edge e1 = new Edge(headnc1, tailnc1);
+                               topoManagerImpl.edgeUpdate(e1, UpdateType.ADDED, props);
+
+                               NodeConnector headnc2 = NodeConnectorCreator.createOFNodeConnector((short) (i+1), headnc1.getNode());
+                               NodeConnector tailnc2 = NodeConnectorCreator.createOFNodeConnector((short)(i+11), tailnc1.getNode());
+                               Edge e2 = new Edge(headnc2, tailnc2);
+                               topoManagerImpl.edgeUpdate(e2, UpdateType.ADDED, props);                                
+               }
+       }
+       
+       @Test
+       public void testGetNodeEdges() throws ConstructionException  {
+               TopologyManagerImpl topoManagerImpl = new TopologyManagerImpl();
+               setNodeEdges(topoManagerImpl);  
+               
+               Map<Node, Set<Edge>> nodeEdgeMap = topoManagerImpl.getNodeEdges();
+               for (Iterator<Map.Entry<Node,Set<Edge>>> i = nodeEdgeMap.entrySet().iterator();  i.hasNext();) {
+                       Map.Entry<Node, Set<Edge>> entry = i.next();
+                       Node node = entry.getKey();
+                       Long nodeId = ((Long) node.getID()).longValue();
+                       Assert.assertTrue((node.getType().equals(NodeIDType.OPENFLOW)));
+                                               
+                       Set<Edge> edges = entry.getValue();
+                       for (Edge edge : edges) {
+                               Long headNcId =  ((Short)edge.getHeadNodeConnector().getID()).longValue();
+                               Long tailNcId = ((Short) edge.getTailNodeConnector().getID()).longValue();
+                               if (nodeId == 1 || nodeId == 3 || nodeId == 5) {
+                                       Assert.assertTrue((headNcId.equals(nodeId) && tailNcId.equals(nodeId + 10)) ||
+                                                                         (headNcId.equals(nodeId + 10) && tailNcId.equals(nodeId)) ||
+                                                                         (headNcId.equals(nodeId + 1) && tailNcId.equals(nodeId + 11)) ||
+                                                                         (headNcId.equals(nodeId + 11) && tailNcId.equals(nodeId + 1)));
+                               } else if (nodeId == 11 || nodeId == 13 || nodeId == 15) {
+                                       Assert.assertTrue((headNcId.equals(nodeId) && tailNcId.equals(nodeId - 10)) ||
+                                                                        (headNcId.equals(nodeId) && tailNcId.equals(nodeId - 10)) ||
+                                                                        (headNcId.equals(nodeId - 9) && tailNcId.equals(nodeId + 1)) ||
+                                                                        (headNcId.equals(nodeId + 1) && tailNcId.equals(nodeId - 9)));
+                               }
+                       }
+                       i.remove();
+               }
+               Assert.assertTrue(nodeEdgeMap.isEmpty());
+       }
+       
+       @Test
+       public void testGetEdges() throws ConstructionException  {
+               TopologyManagerImpl topoManagerImpl = new TopologyManagerImpl();
+               setNodeEdges(topoManagerImpl);
+
+               Map<Edge, Set<Property>> edgeProperty = topoManagerImpl.getEdges();
+               
+               for (Iterator <Map.Entry<Edge, Set<Property>>> i = edgeProperty.entrySet().iterator() ;  i.hasNext();) {
+                       Map.Entry<Edge, Set<Property>> entry = i.next();
+                       Edge e = entry.getKey();
+                       NodeConnector headnc = e.getHeadNodeConnector();
+                       NodeConnector tailnc = e.getTailNodeConnector();
+                       
+                       Long headNodeId = (Long) headnc.getNode().getID();
+                       
+                       Long headNcId =  ((Short)headnc.getID()).longValue();
+                       Long tailNcId = ((Short)tailnc.getID()).longValue();
+                       
+                       if (headNodeId == 1 || headNodeId == 3 || headNodeId == 5) {
+                               Assert.assertTrue((headNcId.equals(headNodeId) && tailNcId.equals(headNodeId + 10)) ||
+                                                                 (headNcId.equals(headNodeId + 10) && tailNcId.equals(headNodeId)) ||
+                                                                 (headNcId.equals(headNodeId + 1) && tailNcId.equals(headNodeId + 11)) ||
+                                                                 (headNcId.equals(headNodeId + 11) && tailNcId.equals(headNodeId + 1)));
+                       } else if (headNodeId == 11 || headNodeId == 13 || headNodeId == 15) {
+                               Assert.assertTrue((headNcId.equals(headNodeId) && tailNcId.equals(headNodeId - 10)) ||
+                                                                (headNcId.equals(headNodeId) && tailNcId.equals(headNodeId - 10)) ||
+                                                                (headNcId.equals(headNodeId - 9) && tailNcId.equals(headNodeId + 1)) ||
+                                                                (headNcId.equals(headNodeId + 1) && tailNcId.equals(headNodeId - 9)));
+                       }
+
+                       Set<Property> prop = entry.getValue();
+                       for (Property p : prop) {
+                               String pName;
+                               long pValue;
+                               if (p instanceof Bandwidth) {
+                                       Bandwidth b = (Bandwidth)p;
+                                       pName = Bandwidth.BandwidthPropName;
+                                       pValue  = b.getValue();
+                                       Assert.assertTrue(pName.equals(p.getName()) && pValue == Bandwidth.BW100Gbps );
+                                       continue;
+                               }
+                               if (p instanceof Latency) {
+                                       Latency l = (Latency)p;
+                                       pName = Latency.LatencyPropName;
+                                       pValue  = l.getValue();
+                                       Assert.assertTrue(pName.equals(p.getName()) && pValue == Latency.LATENCY100ns);
+                                       continue;
+                               }
+                               if (p instanceof State) {
+                                       State state = (State)p;
+                                       pName = State.StatePropName;
+                                       pValue  = state.getValue();
+                                       Assert.assertTrue(pName.equals(p.getName()) && pValue  == State.EDGE_UP);
+                                       continue;
+                               }
+                       }
+                       i.remove();
+               }
+               Assert.assertTrue(edgeProperty.isEmpty());
+       }
+       
+       
+       @Test
+       public void testAddDeleteUserLink () {
+               TopologyUserLinkConfig link1 = new TopologyUserLinkConfig("default1", "1", "2", "1", "2"); 
+               TopologyUserLinkConfig link2 = new TopologyUserLinkConfig("default1", "10", "20", "10", "20"); 
+               TopologyUserLinkConfig link3 = new TopologyUserLinkConfig("default2", "1", "2", "1", "2"); 
+               TopologyUserLinkConfig link4 = new TopologyUserLinkConfig("default20", "10", "20", "10", "20"); 
+               
+               TopologyManagerImpl topoManagerImpl = new TopologyManagerImpl();
+               topoManagerImpl.nonClusterObjectCreate();
+               
+               Assert.assertTrue (topoManagerImpl.addUserLink(link1).isSuccess());             
+               Assert.assertTrue (topoManagerImpl.addUserLink(link2).getCode() == StatusCode.CONFLICT);                
+               Assert.assertTrue (topoManagerImpl.addUserLink(link3).getCode() == StatusCode.CONFLICT);                
+               Assert.assertTrue (topoManagerImpl.addUserLink(link4).isSuccess());             
+               
+               Assert.assertTrue (topoManagerImpl.deleteUserLink(null).getCode() == StatusCode.BADREQUEST);    
+               Assert.assertTrue (topoManagerImpl.deleteUserLink(link1.getName()).isSuccess());        
+               Assert.assertTrue (topoManagerImpl.deleteUserLink(link4.getName()).isSuccess());        
+               Assert.assertTrue (topoManagerImpl.getUserLinks().isEmpty());
+
+       }
+       
+       @Test   
+       public void testGetUserLink () {
+               TopologyUserLinkConfig[] link = new TopologyUserLinkConfig[5];
+               TopologyUserLinkConfig[] reverseLink = new TopologyUserLinkConfig[5];
+               TopologyManagerImpl topoManagerImpl = new TopologyManagerImpl();
+               topoManagerImpl.nonClusterObjectCreate();
+               
+               String name = null;
+               String srcSwitchId = null;
+               String srcPort = null;
+               String dstSwitchId = null;
+               String dstPort = null;
+               
+               /*Creating userlinks and checking for their validity*/
+               link[0] = new TopologyUserLinkConfig(name, srcSwitchId, srcPort, dstSwitchId, dstPort);
+               Assert.assertTrue(link[0].isValid() == false);
+               
+               srcSwitchId = "Z";
+               link[0] = new TopologyUserLinkConfig(name, srcSwitchId, srcPort, dstSwitchId, dstPort); 
+               Assert.assertTrue(link[0].isValid() == false);
+               
+               dstSwitchId = null;
+               link[0] = new TopologyUserLinkConfig(name, srcSwitchId, srcPort, dstSwitchId, dstPort); 
+               Assert.assertTrue(link[0].isValid() == false);
+
+               
+               Integer i;
+               
+               for (i = 0; i < 5; i++) {
+                       link[i] = new TopologyUserLinkConfig(name, srcSwitchId, srcPort, dstSwitchId, dstPort); 
+
+                       name = Integer.toString(i + 1);
+                       srcSwitchId = Integer.toString(i + 1);
+                       srcPort = Integer.toString(i + 1);
+                       dstSwitchId = Integer.toString((i + 1)*10);
+                       dstPort = Integer.toString((i + 1)*10);
+                       
+                       link[i].setName(name);
+                       link[i].setSrcSwitchId(srcSwitchId);
+                       link[i].setSrcPort(srcPort);
+                       link[i].setDstSwitchId(dstSwitchId);
+                       link[i].setDstPort(dstPort);
+                       
+                       Assert.assertTrue(link[i].isValid() == true);
+
+                       reverseLink[i] = new TopologyUserLinkConfig(name, dstSwitchId, dstPort, srcSwitchId, srcPort); 
+
+                       topoManagerImpl.addUserLink(link[i]);
+               }
+               ConcurrentMap<String, TopologyUserLinkConfig> userLinks = topoManagerImpl.getUserLinks();
+               TopologyUserLinkConfig resultLink;
+
+               for (i = 0; i < 5; i++) {
+                       resultLink = userLinks.get(((Integer)(i + 1)).toString());
+                                                       
+                       Assert.assertTrue(resultLink.getName().equals(reverseLink[i].getName()));
+                       Assert.assertTrue(resultLink.getDstSwitchId().equals(reverseLink[i].getSrcSwitchId()));
+                       Assert.assertTrue(resultLink.getDstPort().equals(reverseLink[i].getSrcPort()));
+                       Assert.assertTrue(resultLink.getSrcSwitchId().equals(reverseLink[i].getDstSwitchId()));
+                       Assert.assertTrue(resultLink.getSrcPort().equals(reverseLink[i].getDstPort()));
+               }
+       }
+       
+       @Test
+        public void testHostLinkMethods() throws ConstructionException, UnknownHostException  {
+               TopologyManagerImpl topoManagerImpl = new TopologyManagerImpl();
+               topoManagerImpl.nonClusterObjectCreate();
+               int hostCounter = 0;
+               
+               State state;
+               Bandwidth bw;
+               Latency l;
+               Set<Property> props = new HashSet<Property>();
+               state = new State(State.EDGE_UP);
+               bw = new Bandwidth(Bandwidth.BW100Gbps);
+               l = new Latency(Latency.LATENCY100ns);
+               props.add(state);
+               props.add(bw);
+               props.add(l);
+
+               EthernetAddress ea;
+               InetAddress ip;
+               Host[] h = new Host[5];
+               NodeConnector[] nc = new NodeConnector[5];
+               
+               /* Adding host, nodeConnector to hostsDB for the i = 0,1,2,3.  No host
+                * added for i = 4
+                */
+               for (int i = 0; i < 5; i++) {
+                       if (hostCounter < 4) {
+                               ea = new EthernetAddress(new byte[]{(byte)0x0, (byte)0x0,
+                                               (byte)0x0, (byte)0x0,
+                                               (byte)0x0, (byte)i});
+                               String stringIP = new StringBuilder().append(i + 1).append(".").append(i+10).append(".").append(i+20).append(".").append(i+30).toString();
+                               ip = InetAddress.getByName(stringIP);
+                               h[hostCounter] = new Host(ea, ip);
+                       } else {
+                               h[hostCounter] = null;
+                       }
+                       hostCounter++;
+                       nc[i] = NodeConnectorCreator.createOFNodeConnector((short)(i + 1), NodeCreator.createOFNode((long)(i + 1)));
+                       topoManagerImpl.updateHostLink(nc[i], h[i], UpdateType.ADDED, props);
+               }
+               
+               for (int i = 0; i < 5; i++) {
+                       Host host = topoManagerImpl.getHostAttachedToNodeConnector(nc[i]);
+                       if (i == 4)
+                               Assert.assertTrue(host == null);
+                       else
+                               Assert.assertTrue(host.equals(h[i]));                   
+               }
+               
+               Set<NodeConnector> ncSet =      topoManagerImpl.getNodeConnectorWithHost();
+               for (int i = 0; i < 5; i++) {
+                       Assert.assertTrue(ncSet.remove(nc[i]));
+               }
+               Assert.assertTrue(ncSet.isEmpty());
+       }
+       
+       @Test
+       public void testGetNodesWithNodeConnectorHost() throws ConstructionException, UnknownHostException {
+               TopologyManagerImpl topoManagerImpl = new TopologyManagerImpl();
+               topoManagerImpl.nonClusterObjectCreate();
+               int hostCounter = 0;
+               
+               State state;
+               Bandwidth bw;
+               Latency l;
+               Set<Property> props = new HashSet<Property>();
+               state = new State(State.EDGE_UP);
+               bw = new Bandwidth(Bandwidth.BW100Gbps);
+               l = new Latency(Latency.LATENCY100ns);
+               props.add(state);
+               props.add(bw);
+               props.add(l);
+
+               EthernetAddress ea;
+               InetAddress ip;
+               Host[] h = new Host[5];
+               NodeConnector[] nc = new NodeConnector[5];
+               
+               /*Adding host, nodeconnector, properties of edge to hostsDB for the first three nodes only*/
+               for (int i = 1; i < 6; i++) {
+                       if (i < 4) {
+                               ea = new EthernetAddress(new byte[]{(byte)0x0, (byte)0x0,
+                                               (byte)0x0, (byte)0x0,
+                                               (byte)0x0, (byte)i});
+                               String stringIP = new StringBuilder().append(i).append(".").append(i+10).append(".").append(i+20).append(".").append(i+30).toString();
+                               ip = InetAddress.getByName(stringIP);
+                               h[hostCounter] = new Host(ea, ip);
+                       }
+                       else {
+                               h[hostCounter] = null;
+                       }
+                       hostCounter++;
+                       nc[i - 1] = NodeConnectorCreator.createOFNodeConnector((short)i, NodeCreator.createOFNode((long)i));
+                       topoManagerImpl.updateHostLink(nc[i - 1], h[i - 1], UpdateType.ADDED, props);
+               }
+               
+               /*Get the nodes which have host connected to its nodeConnector*/
+               Map<Node, Set<NodeConnector>> nodeNCmap = topoManagerImpl.getNodesWithNodeConnectorHost();
+               for (int i = 1; i < 6; i++) {
+                       Node node = nc[i - 1].getNode();
+                       Set<NodeConnector> ncSet = nodeNCmap.get(nc[i - 1].getNode());
+
+                       Assert.assertTrue(ncSet == nodeNCmap.remove(node));
+               }
+       
+               Assert.assertTrue(nodeNCmap.isEmpty());
+       }
+}
+
diff --git a/opendaylight/usermanager/pom.xml b/opendaylight/usermanager/pom.xml
new file mode 100644 (file)
index 0000000..4c83a3e
--- /dev/null
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+       xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../commons/opendaylight</relativePath>
+       </parent>
+
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>usermanager</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.clustering.services,
+                                                       org.opendaylight.controller.configuration,
+                                                       org.opendaylight.controller.sal.authorization,
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.packet,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.switchmanager,
+                                                       org.opendaylight.controller.containermanager,
+                                                       org.slf4j,
+                                                       org.eclipse.osgi.framework.console,
+                                                       org.osgi.framework,
+                                                       org.apache.felix.dm,
+                                                       org.apache.commons.lang3.builder,
+                                                       org.apache.commons.logging,
+                                                       javax.servlet,
+                                                       javax.servlet.http,
+                                                       org.springframework.security.web.context,
+                                                       org.springframework.security.core,
+                                                       org.springframework.security.core.context,
+                                                       org.apache.commons.lang3,
+                                                       org.springframework.security.authentication,
+                                                       org.springframework.security.core.authority,
+                                                       org.springframework.security.core.userdetails
+
+                                               </Import-Package>
+                                               <Export-Package>
+                                                       org.opendaylight.controller.usermanager,
+                                                       org.opendaylight.controller.usermanager.internal                                                
+                                               </Export-Package>
+                                               <Bundle-Activator>
+                                                       org.opendaylight.controller.usermanager.internal.Activator
+                                               </Bundle-Activator>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>switchmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>containermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>clustering.services</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>configuration</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/AuthResponse.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/AuthResponse.java
new file mode 100644 (file)
index 0000000..062c358
--- /dev/null
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opendaylight.controller.sal.authorization.AuthResultEnum;
+
+/**
+ * The class describes AAA response status and payload data
+ */
+public class AuthResponse implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private List<String> data;
+    private AuthResultEnum status;
+    private AuthResultEnum authorStatus;
+
+    public AuthResponse() {
+        this.data = new LinkedList<String>();
+        this.status = AuthResultEnum.AUTH_NONE;
+        this.authorStatus = AuthResultEnum.AUTH_NONE;
+    }
+
+    public void setData(List<String> data) {
+        this.data = data;
+    }
+
+    public void addData(String data) {
+        this.data.add(data);
+    }
+
+    public List<String> getData() {
+        return data;
+    }
+
+    public void setStatus(AuthResultEnum status) {
+        this.status = status;
+    }
+
+    public AuthResultEnum getStatus() {
+        return status;
+    }
+
+    public void setAuthorizationStatus(AuthResultEnum authorStatus) {
+        this.authorStatus = authorStatus;
+    }
+
+    public AuthResultEnum getAuthorizationStatus() {
+        return authorStatus;
+    }
+
+    public String toString() {
+        return ("\nReceived messages: " + data.toString() + "\nStatus: " + status);
+    }
+
+    public void resetData(String rolesData) {
+        // TODO Auto-generated method stub
+
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/IAAAProvider.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/IAAAProvider.java
new file mode 100644 (file)
index 0000000..2e330d8
--- /dev/null
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager;
+
+/**
+ * IAAAProvider exposes a pluggable interface for 3rd party Authentication and Authorization
+ * providers to support the UserManager with AAA management.
+ */
+
+public interface IAAAProvider {
+
+    /**
+     * Authenticate user with AAA server and return authentication and authorization info
+     * using the Provider's mechanism
+     * @param userName
+     * @param password
+     * @param server
+     * @param secretKey
+     * @return Authentication and Authorization Response
+     */
+    public AuthResponse authService(String userName, String password,
+            String server, String secretKey);
+
+    /**
+     * Returns the Name of the Provider
+     *
+     * @return Name of the AAA provider
+     */
+    public String getName();
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/ISessionManager.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/ISessionManager.java
new file mode 100644 (file)
index 0000000..1a423a2
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager;
+
+import javax.servlet.http.HttpSessionListener;
+
+public interface ISessionManager extends HttpSessionListener {
+
+    public void invalidateSessions(String username, String sessionId);
+
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/IUserManager.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/IUserManager.java
new file mode 100644 (file)
index 0000000..aac5aba
--- /dev/null
@@ -0,0 +1,208 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.sal.authorization.AuthResultEnum;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.usermanager.internal.AuthorizationConfig;
+import org.opendaylight.controller.usermanager.internal.ServerConfig;
+import org.opendaylight.controller.usermanager.internal.UserConfig;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+/**
+ * The Interface which describes the methods exposed by User Manager.
+ */
+public interface IUserManager extends UserDetailsService {
+
+    /**
+     * Returns the list of roles associated to the passed user name
+     *
+     * @param userName
+     * @return the role associated to the user name
+     */
+    public List<String> getUserRoles(String userName);
+
+    /**
+     * Authenticate user with AAA server and return authentication and authorization info
+     *
+     * @param username the username
+     * @param password the password
+     * @return                 {@link org.opendaylight.controller.sal.authorization.AuthResultEnum authenticate result}
+     */
+    public AuthResultEnum authenticate(String username, String password);
+
+    /**
+     * Add/remove AAA server
+     *
+     * @param configObject     refer to {@link org.opendaylight.controller.usermanager.internal.ServerConfig ServerConfig}
+     * @return                         status code
+     */
+    public Status addAAAServer(ServerConfig configObject);
+
+    /**
+     * Remove AAA server
+     *
+     * @param configObject     refer to {@link org.opendaylight.controller.usermanager.internal.ServerConfig ServerConfig}
+     * @return                         status code
+     */
+    public Status removeAAAServer(ServerConfig configObject);
+
+    /**
+     * Add a local user
+     *
+     * @param configObject refer to {@link org.opendaylight.controller.usermanager.internal.UserConfig UserConfig}
+     * @return                         status code
+     */
+    public Status addLocalUser(UserConfig configObject);
+
+    /**
+     * Remove a local user
+     *
+     * @param configObject refer to {@link org.opendaylight.controller.usermanager.internal.UserConfig UserConfig}
+     * @return                         status code
+     */
+    public Status removeLocalUser(UserConfig configObject);
+    
+    /**
+     * Remove a local user
+     * 
+     * @param userName the user name
+     * @return the status of this action
+     */
+    public Status removeLocalUser(String userName);
+
+    /**
+     * Add the authorization information for a user that gets authenticated remotely
+     *
+     * @param AAAconf
+     * @return
+     */
+    public Status addAuthInfo(AuthorizationConfig AAAconf);
+
+    /**
+     * Remove the authorization information for a user that gets authenticated remotely
+     *
+     * @param AAAconf
+     * @return
+     */
+    public Status removeAuthInfo(AuthorizationConfig AAAconf);
+
+    /**
+     * Return the list of authorization resources
+     * @return
+     */
+    public List<AuthorizationConfig> getAuthorizationList();
+
+    /**
+     * Returns a list of AAA Providers.
+     * @return Set of provider names.
+     */
+    public Set<String> getAAAProviderNames();
+
+    /**
+     * Change the current password for a configured user
+     *
+     * @param user
+     * @param curPasssword
+     * @param newPassword
+     * @return
+     */
+    public Status changeLocalUserPassword(String user, String curPassword,
+            String newPassword);
+
+    /**
+     * Return a list of AAA servers currently configured
+     *
+     * @return list of {@link org.opendaylight.controller.usermanager.internal.ServerConfig ServerConfig}
+     */
+    public List<ServerConfig> getAAAServerList();
+
+    /**
+     * Return a list of local users
+     *
+     * @return list of {@link org.opendaylight.controller.usermanager.internal.UserConfig UserConfig}
+     */
+    public List<UserConfig> getLocalUserList();
+
+    /**
+     * Save the local users to local disk
+     *
+     * @return status code
+     */
+    public Status saveLocalUserList();
+
+    /**
+     * Save the AAA server configurations to local disk
+     *
+     * @return status code
+     */
+    public Status saveAAAServerList();
+
+    /**
+     * Save the Authorization configurations to local disk
+     *
+     * @return status code
+     */
+    public Status saveAuthorizationList();
+
+    /**
+     * Remove user profile when user logs out
+     *
+     * @param username the user name
+     */
+    public void userLogout(String username);
+
+    /**
+     * Remove user profile when user times out
+     *
+     * @param username the user name
+     */
+    public void userTimedOut(String username);
+
+    /**
+     * Get the list of users currently logged in
+     *
+     * @return the list of users along with their administrative roles
+     */
+    public Map<String, List<String>> getUserLoggedIn();
+
+    /**
+     * Get date and time user was successfully authenticated
+     *
+     * @param user
+     * @return Date in String format
+     */
+    public String getAccessDate(String user);
+
+    /**
+     * Returns the user level for the passed user name
+     * It check the roles assigned to this user and checks
+     * against the well known Controller user roles to
+     * determines the highest user level associated with
+     * the user
+     *
+     * @param userName the user name
+     * @return the highest user level for this user
+     */
+    public UserLevel getUserLevel(String userName);
+
+    // For internal use. Place holder to move securityContext storage.
+    public SecurityContextRepository getSecurityContextRepo();
+
+    // Session manager to implement session mgmt across web-apps
+    public ISessionManager getSessionManager();
+
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/ODLUserLevel.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/ODLUserLevel.java
new file mode 100644 (file)
index 0000000..9766fd4
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.springframework.security.core.GrantedAuthority;
+
+public class ODLUserLevel implements GrantedAuthority {
+       private static final long serialVersionUID = 1L;
+       UserLevel userLevel;
+
+    public ODLUserLevel(UserLevel userLevel) {
+        this.userLevel = userLevel;
+    }
+
+    @Override
+    public String getAuthority() {
+        return "ROLE_" + this.userLevel.toString().toUpperCase();
+    }
+
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/Activator.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/Activator.java
new file mode 100644 (file)
index 0000000..a1fdce5
--- /dev/null
@@ -0,0 +1,147 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.configuration.IConfigurationAware;
+import org.opendaylight.controller.containermanager.IContainerAuthorization;
+import org.opendaylight.controller.sal.authorization.IResourceAuthorization;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.usermanager.IAAAProvider;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * UserManager Bundle Activator
+ *
+ *
+ */
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some
+     * initializations are done by the
+     * ComponentActivatorAbstractBase.
+     *
+     */
+    public void init() {
+
+    }
+
+    /**
+     * Function called when the activator stops just before the
+     * cleanup done by ComponentActivatorAbstractBase
+     *
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the
+     * list of known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     * instantiated in order to get an fully working implementation
+     * Object
+     */
+    public Object[] getImplementations() {
+        return null;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies
+     * is required.
+     *
+     * @param c dependency manager Component object, used for
+     * configuring the dependencies exported and imported
+     * @param imp Implementation class that is being configured,
+     * needed as long as the same routine can configure multiple
+     * implementations
+     * @param containerName The containerName being configured, this allow
+     * also optional per-container different behavior if needed, usually
+     * should not be the case though.
+     */
+    public void configureInstance(Component c, Object imp, String containerName) {
+    }
+
+    /**
+     * Method which tells how many global implementations are
+     * supported by the bundle. This way we can tune the number of
+     * components created. This components will be created ONLY at the
+     * time of bundle startup and will be destroyed only at time of
+     * bundle destruction, this is the major difference with the
+     * implementation retrieved via getImplementations where all of
+     * them are assumed to be in a container !
+     *
+     *
+     * @return The list of implementations the bundle will support,
+     * in Global version
+     */
+    protected Object[] getGlobalImplementations() {
+        Object[] res = { UserManagerImpl.class };
+        return res;
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c Component assigned for this instance, this will be
+     * what will be used for configuration
+     * @param imp implementation to be configured
+     * @param containerName container on which the configuration happens
+     */
+    protected void configureGlobalInstance(Component c, Object imp) {
+        if (imp.equals(UserManagerImpl.class)) {
+            // export the service
+            Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+            Set<String> propSet = new HashSet<String>();
+            propSet.add("usermanager.localUserSaveConfigEvent");
+            propSet.add("usermanager.remoteServerSaveConfigEvent");
+            propSet.add("usermanager.authorizationSaveConfigEvent");
+            props.put("cachenames", propSet);
+
+            // export the service
+            c.setInterface(new String[] { ICacheUpdateAware.class.getName(),
+                    IUserManager.class.getName(),
+                    IConfigurationAware.class.getName() }, props);
+
+            c.add(createServiceDependency().setService(
+                    IClusterGlobalServices.class).setCallbacks(
+                    "setClusterGlobalService", "unsetClusterGlobalService")
+                    .setRequired(true));
+
+            c.add(createServiceDependency().setService(IAAAProvider.class)
+                    .setCallbacks("addAAAProvider", "removeAAAProvider")
+                    .setRequired(false));
+
+            c.add(createServiceDependency().setService(
+                    IContainerAuthorization.class).setCallbacks(
+                    "setContainerAuthClient", "unsetContainerAuthClient")
+                    .setRequired(false));
+
+            c.add(createServiceDependency().setService(
+                    IResourceAuthorization.class).setCallbacks(
+                    "setAppAuthClient", "unsetAppAuthClient")
+                    .setRequired(false));
+        }
+    }
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/AuthenticatedUser.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/AuthenticatedUser.java
new file mode 100644 (file)
index 0000000..6c6f07c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.usermanager.ODLUserLevel;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+/**
+ * Represents a user that was successfully authenticated and authorized
+ * It contains the user role for which the user was authorized and the
+ * date on which it was authenticated and authorized
+ */
+public class AuthenticatedUser implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private List<String> userRoles;
+    private Date accessDate;
+
+    public AuthenticatedUser(String name) {
+        userRoles = null;
+        accessDate = new Date();
+    }
+
+    public void setRoleList(List<String> roleList) {
+        this.userRoles = roleList;
+    }
+
+    public void setRoleList(String[] roleArray) {
+        userRoles = new ArrayList<String>(roleArray.length);
+        for (String role : roleArray) {
+            userRoles.add(role);
+        }
+    }
+
+    public List<String> getUserRoles() {
+        return userRoles;
+    }
+
+    public void addUserRole(String string) {
+        userRoles.add(string);
+    }
+
+    public String getAccessDate() {
+        return accessDate.toString();
+    }
+
+    public List<GrantedAuthority> getGrantedAuthorities(UserLevel usrLvl) {
+        List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
+        roles.add(new SimpleGrantedAuthority(new ODLUserLevel(usrLvl)
+                .getAuthority()));
+        return roles;
+    }
+
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/AuthorizationConfig.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/AuthorizationConfig.java
new file mode 100644 (file)
index 0000000..3eafe2b
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+
+/**
+ * Configuration Java Object which represents a Local configured Authorization
+ * for a remote authenticated user for User Manager.
+ */
+public class AuthorizationConfig extends UserConfig {
+       private static final long serialVersionUID = 1L;
+
+       public AuthorizationConfig() {
+               super();
+       }
+
+       // Constructor may be needed for autocontainer logic
+       public AuthorizationConfig(String user, String role) {
+               super();
+               this.user = user;
+               this.role = role;
+       }
+
+       @Override
+       public boolean isValid() {
+               return (user != null && !user.isEmpty() && role != null && !role
+                               .isEmpty());
+       }
+
+       public String getRolesData() {
+               return (role.replace(",", " "));
+       }
+
+       public String toString() {
+               return "AuthorizationConfig=[user: " + user + ", role: " + role + "]";
+       }
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/ServerConfig.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/ServerConfig.java
new file mode 100644 (file)
index 0000000..4ab0ae2
--- /dev/null
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+/**
+ * Configuration Java Object which represents a Remote AAA server configuration
+ * information for User Manager.
+ */
+public class ServerConfig implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // Order matters: JSP file expects following fields in the following order
+    private String ip;
+    private String secret;
+    private String protocol;
+
+    public ServerConfig() {
+    }
+
+    public String getAddress() {
+        return ip;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public boolean isValid() {
+        return (ip != null && !ip.isEmpty() && secret != null && !secret
+                .isEmpty());
+    }
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/UserConfig.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/UserConfig.java
new file mode 100644 (file)
index 0000000..884ab87
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.opendaylight.controller.sal.authorization.AuthResultEnum;
+import org.opendaylight.controller.usermanager.AuthResponse;
+
+/**
+ * Configuration Java Object which represents a Local AAA user
+ * configuration information for User Manager. 
+ */
+public class UserConfig implements Serializable {
+       private static final long serialVersionUID = 1L;
+
+       /*
+        * Clear text password as we are moving to some MD5 digest
+        * for when saving configurations
+        */
+       protected String user;
+       protected String role;
+       private String password;
+
+       public UserConfig() {
+       }
+
+       public UserConfig(String user, String password, String role) {
+               this.user = user;
+               this.password = password;
+               this.role = role;
+       }
+
+       public String getUser() {
+               return user;
+       }
+
+       public String getPassword() {
+               return password;
+       }
+
+       public String getRole() {
+               return role;
+       }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+    
+    @Override
+    public String toString() {
+       return "UserConfig[user="+ user + ", password=" + password + "]";
+    }
+
+       public boolean isValid() {
+               return (user != null && !user.isEmpty() && role != null
+                               && !role.isEmpty() && password != null && !password.isEmpty());
+       }
+
+       public boolean update(String currentPassword, String newPassword,
+                       String newRole) {
+               // To make any changes to a user configured profile, current password
+               // must always be provided
+               if (!this.password.equals(currentPassword)) {
+                       return false;
+               }
+               if (newPassword != null) {
+                       this.password = newPassword;
+               }
+               if (newRole != null) {
+                       this.role = newRole;
+               }
+               return true;
+       }
+
+       public AuthResponse authenticate(String clearTextPass) {
+               AuthResponse locResponse = new AuthResponse();
+               if (password.equals(clearTextPass)) {
+                       locResponse.setStatus(AuthResultEnum.AUTH_ACCEPT_LOC);
+                       locResponse.addData(role.replace(",", " "));
+               } else {
+                       locResponse.setStatus(AuthResultEnum.AUTH_REJECT_LOC);
+               }
+               return locResponse;
+       }
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/UserManagerImpl.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/internal/UserManagerImpl.java
new file mode 100644 (file)
index 0000000..7147c0a
--- /dev/null
@@ -0,0 +1,981 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.configuration.IConfigurationAware;
+import org.opendaylight.controller.containermanager.IContainerAuthorization;
+import org.opendaylight.controller.sal.authorization.AuthResultEnum;
+import org.opendaylight.controller.sal.authorization.IResourceAuthorization;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.IObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectReader;
+import org.opendaylight.controller.sal.utils.ObjectWriter;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.usermanager.AuthResponse;
+import org.opendaylight.controller.usermanager.IAAAProvider;
+import org.opendaylight.controller.usermanager.ISessionManager;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.usermanager.security.SessionManager;
+import org.opendaylight.controller.usermanager.security.UserSecurityContextRepository;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+/**
+ * The internal implementation of the User Manager.
+ */
+public class UserManagerImpl implements IUserManager, IObjectReader,
+        IConfigurationAware, ICacheUpdateAware<Long, String>, CommandProvider,
+        AuthenticationProvider {
+    private static final Logger logger = LoggerFactory
+            .getLogger(UserManagerImpl.class);
+    private static final String defaultAdmin = "admin";
+    private static final String defaultAdminPassword = "admin";
+    private static final String defaultAdminRole = UserLevel.NETWORKADMIN
+            .toString();
+    private static final String ROOT = GlobalConstants.STARTUPHOME.toString();
+    private static final String SAVE = "save";
+    private static final String usersFileName = ROOT + "users.conf";
+    private static final String serversFileName = ROOT + "servers.conf";
+    private static final String authFileName = ROOT + "authorization.conf";
+    private ConcurrentMap<String, UserConfig> localUserConfigList;
+    private ConcurrentMap<String, ServerConfig> remoteServerConfigList;
+    private ConcurrentMap<String, AuthorizationConfig> authorizationConfList; // local authorization info for remotely authenticated users
+    private ConcurrentMap<String, AuthenticatedUser> activeUsers;
+    private ConcurrentMap<String, IAAAProvider> authProviders;
+    private ConcurrentMap<Long, String> localUserListSaveConfigEvent,
+            remoteServerSaveConfigEvent, authorizationSaveConfigEvent;
+    private IClusterGlobalServices clusterGlobalService = null;
+    private SecurityContextRepository securityContextRepo = new UserSecurityContextRepository();
+    private IContainerAuthorization containerAuthorizationClient;
+    private Set<IResourceAuthorization> applicationAuthorizationClients;
+    private ISessionManager sessionMgr = new SessionManager();
+
+    public boolean addAAAProvider(IAAAProvider provider) {
+        if (provider == null
+                       || provider.getName() == null
+                || provider.getName().trim().isEmpty()) {
+            return false;
+        }
+        if (authProviders.get(provider.getName()) != null) {
+            return false;
+        }
+
+        authProviders.put(provider.getName(), provider);
+        return true;
+    }
+
+    public void removeAAAProvider(IAAAProvider provider) {
+        authProviders.remove(provider.getName());
+    }
+
+    public IAAAProvider getAAAProvider(String name) {
+        return authProviders.get(name);
+    }
+
+    public Set<String> getAAAProviderNames() {
+        return authProviders.keySet();
+    }
+
+    @SuppressWarnings("deprecation")
+    private void allocateCaches() {
+        this.applicationAuthorizationClients = Collections
+                .synchronizedSet(new HashSet<IResourceAuthorization>());
+        if (clusterGlobalService == null) {
+            logger
+                    .error("un-initialized clusterGlobalService, can't create cache");
+            return;
+        }
+
+        try {
+            clusterGlobalService.createCache("usermanager.localUserConfigList",
+                    EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterGlobalService.createCache(
+                    "usermanager.remoteServerConfigList", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterGlobalService.createCache(
+                    "usermanager.authorizationConfList", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterGlobalService.createCache("usermanager.activeUsers", EnumSet
+                    .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterGlobalService.createCache(
+                    "usermanager.localUserSaveConfigEvent", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterGlobalService.createCache(
+                    "usermanager.remoteServerSaveConfigEvent", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+
+            clusterGlobalService.createCache(
+                    "usermanager.authorizationSaveConfigEvent", EnumSet
+                            .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
+        } catch (CacheConfigException cce) {
+            logger.error("\nCache configuration invalid - check cache mode");
+        } catch (CacheExistException ce) {
+            logger
+                    .error("\nCache already exits - destroy and recreate if needed");
+        }
+    }
+
+    @SuppressWarnings( { "unchecked", "deprecation" })
+    private void retrieveCaches() {
+        if (clusterGlobalService == null) {
+            logger.error("un-initialized clusterService, can't retrieve cache");
+            return;
+        }
+
+        activeUsers = (ConcurrentMap<String, AuthenticatedUser>) clusterGlobalService
+                .getCache("usermanager.activeUsers");
+        if (activeUsers == null) {
+            logger.error("\nFailed to get cache for activeUsers");
+        }
+
+        localUserConfigList = (ConcurrentMap<String, UserConfig>) clusterGlobalService
+                .getCache("usermanager.localUserConfigList");
+        if (localUserConfigList == null) {
+            logger.error("\nFailed to get cache for localUserConfigList");
+        }
+
+        remoteServerConfigList = (ConcurrentMap<String, ServerConfig>) clusterGlobalService
+                .getCache("usermanager.remoteServerConfigList");
+        if (remoteServerConfigList == null) {
+            logger.error("\nFailed to get cache for remoteServerConfigList");
+        }
+
+        authorizationConfList = (ConcurrentMap<String, AuthorizationConfig>) clusterGlobalService
+                .getCache("usermanager.authorizationConfList");
+        if (authorizationConfList == null) {
+            logger.error("\nFailed to get cache for authorizationConfList");
+        }
+
+        localUserListSaveConfigEvent = (ConcurrentMap<Long, String>) clusterGlobalService
+                .getCache("usermanager.localUserSaveConfigEvent");
+        if (localUserListSaveConfigEvent == null) {
+            logger.error("\nFailed to get cache for localUserSaveConfigEvent");
+        }
+
+        remoteServerSaveConfigEvent = (ConcurrentMap<Long, String>) clusterGlobalService
+                .getCache("usermanager.remoteServerSaveConfigEvent");
+        if (remoteServerSaveConfigEvent == null) {
+            logger
+                    .error("\nFailed to get cache for remoteServerSaveConfigEvent");
+        }
+
+        authorizationSaveConfigEvent = (ConcurrentMap<Long, String>) clusterGlobalService
+                .getCache("usermanager.authorizationSaveConfigEvent");
+        if (authorizationSaveConfigEvent == null) {
+            logger
+                    .error("\nFailed to get cache for authorizationSaveConfigEvent");
+        }
+    }
+
+    private void loadConfigurations() {
+       // To encode and decode user and server configuration objects
+       loadSecurityKeys();
+       
+        /*
+         * Do not load local startup file if we already got the
+         * configurations synced from another cluster node
+         */
+        if (localUserConfigList.isEmpty()) {
+            loadUserConfig();
+        }
+        if (remoteServerConfigList.isEmpty()) {
+            loadServerConfig();
+        }
+        if (authorizationConfList.isEmpty()) {
+            loadAuthConfig();
+        }
+    }
+
+    private void loadSecurityKeys() {
+               
+       }
+
+       private void checkDefaultNetworkAdmin() {
+        // If startup config is not there, it's old or it was deleted, 
+               // need to add Default Admin
+        if (!localUserConfigList.containsKey(defaultAdmin)) {
+               localUserConfigList.put(defaultAdmin,
+                                               new UserConfig(defaultAdmin,
+                                                               defaultAdminPassword,
+                                                       defaultAdminRole));
+        }
+    }
+
+    @Override
+    public AuthResultEnum authenticate(String userName, String password) {
+        IAAAProvider aaaClient;
+        AuthResponse rcResponse = null;
+        AuthenticatedUser result;
+        String[] adminRoles = null;
+        boolean remotelyAuthenticated = false;
+        boolean authorizationInfoIsPresent = false;
+        boolean authorized = false;
+
+        /*
+         * Attempt remote authentication first if server is configured
+         */
+        for (ServerConfig aaaServer : remoteServerConfigList.values()) {
+            String protocol = aaaServer.getProtocol();
+            aaaClient = this.getAAAProvider(protocol);
+            if (aaaClient != null) {
+                rcResponse = aaaClient.authService(userName, password,
+                        aaaServer.getAddress(), aaaServer.getSecret());
+                if (rcResponse.getStatus() == AuthResultEnum.AUTH_ACCEPT) {
+                    logger
+                            .info(
+                                    "Remote Authentication Succeeded for User: \"{}\", by Server: {}",
+                                    userName, aaaServer.getAddress());
+                    remotelyAuthenticated = true;
+                    break;
+                } else if (rcResponse.getStatus() == AuthResultEnum.AUTH_REJECT) {
+                    logger.info(
+                            "Remote Authentication Rejected User: \"{}\", from Server: {}, Reason: "
+                                    + rcResponse.getStatus().toString(),
+                            userName, aaaServer.getAddress());
+                } else {
+                    logger.info(
+                            "Remote Authentication Failed for User: \"{}\", from Server: {}, Reason: "
+                                    + rcResponse.getStatus().toString(),
+                            userName, aaaServer.getAddress());
+                }
+            }
+        }
+
+        if (!remotelyAuthenticated) {
+            UserConfig localUser = this.localUserConfigList.get(userName);
+            if (localUser == null) {
+                logger.info(
+                        "Local Authentication Failed for User:\"{}\", Reason: "
+                                + "user not found in Local Database", userName);
+                return (AuthResultEnum.AUTH_INVALID_LOC_USER);
+            }
+            rcResponse = localUser.authenticate(password);
+            if (rcResponse.getStatus() != AuthResultEnum.AUTH_ACCEPT_LOC) {
+                logger.info("Local Authentication Failed for User: \"{}\", Reason: {}",
+                                userName, rcResponse.getStatus().toString());
+                
+                return (rcResponse.getStatus());
+            }
+            logger.info("Local Authentication Succeeded for User: \"{}\"",
+                    userName);
+        }
+
+        /*
+         * Authentication succeeded
+         */
+        result = new AuthenticatedUser(userName);
+
+        /*
+         * Extract attributes from response
+         * All the information we are interested in is in the first Cisco VSA (vendor specific attribute).
+         * Just process the first VSA and return
+         */
+        String attributes = (rcResponse.getData() != null && !rcResponse
+                .getData().isEmpty()) ? rcResponse.getData().get(0) : null;
+
+        /*
+         * Check if the authorization information is present
+         */
+        authorizationInfoIsPresent = checkAuthorizationInfo(attributes);
+
+        /*
+         * The AAA server was only used to perform the authentication
+         * Look for locally stored authorization info for this user
+         * If found, add the data to the rcResponse
+         */
+        if (remotelyAuthenticated && !authorizationInfoIsPresent) {
+            logger
+                    .info(
+                            "No Remote Authorization Info provided by Server for User: \"{}\"",
+                            userName);
+            logger.info(
+                    "Looking for Local Authorization Info for User: \"{}\"",
+                    userName);
+
+            AuthorizationConfig resource = authorizationConfList.get(userName);
+            if (resource != null) {
+                logger.info("Found Local Authorization Info for User: \"{}\"",
+                        userName);
+                attributes = resource.getRolesData();
+
+            }
+            authorizationInfoIsPresent = checkAuthorizationInfo(attributes);
+        }
+
+        /*
+         * Common response parsing for local & remote authenticated user
+         * Looking for authorized resources, detecting attributes' validity
+         */
+        if (authorizationInfoIsPresent) {
+               // Identifying the administrative role
+            adminRoles = attributes.split(" ");
+            result.setRoleList(adminRoles);
+            authorized = true;
+        } else {
+            logger.info("Not able to find Authorization Info for User: \"{}\"",
+                    userName);
+        }
+
+        /*
+         * Add profile for authenticated user
+         */
+        putUserInActiveList(userName, result);
+        if (authorized) {
+            logger.info("User \"{}\" authorized for the following role(s): "
+                    + result.getUserRoles(), userName);
+        } else {
+            logger.info("User \"{}\" Not Authorized for any role ", userName);
+        }
+
+        return rcResponse.getStatus();
+    }
+
+    // Check in the attributes string whether or not authorization information is present
+    private boolean checkAuthorizationInfo(String attributes) {
+        return (attributes != null && !attributes.isEmpty());
+    }
+
+    private void putUserInActiveList(String user, AuthenticatedUser result) {
+        activeUsers.put(user, result);
+    }
+
+    private void removeUserFromActiveList(String user) {
+        if (!activeUsers.containsKey(user)) {
+            // as cookie persists in cache, we can get logout for unexisting active users
+            return;
+        }
+        activeUsers.remove(user);
+    }
+
+    public Status saveLocalUserList() {
+        // Publish the save config event to the cluster nodes
+        localUserListSaveConfigEvent.put(new Date().getTime(), SAVE);
+        return saveLocalUserListInternal();
+    }
+
+    private Status saveLocalUserListInternal() {
+        ObjectWriter objWriter = new ObjectWriter();
+        return objWriter.write(new ConcurrentHashMap<String, UserConfig>(
+                localUserConfigList), usersFileName);
+    }
+
+    public Status saveAAAServerList() {
+        // Publish the save config event to the cluster nodes
+        remoteServerSaveConfigEvent.put(new Date().getTime(), SAVE);
+        return saveAAAServerListInternal();
+    }
+
+    private Status saveAAAServerListInternal() {
+        ObjectWriter objWriter = new ObjectWriter();
+        return objWriter.write(new ConcurrentHashMap<String, ServerConfig>(
+                remoteServerConfigList), serversFileName);
+    }
+
+    public Status saveAuthorizationList() {
+        // Publish the save config event to the cluster nodes
+        authorizationSaveConfigEvent.put(new Date().getTime(), SAVE);
+        return saveAuthorizationListInternal();
+    }
+
+    private Status saveAuthorizationListInternal() {
+        ObjectWriter objWriter = new ObjectWriter();
+        return objWriter.write(
+                new ConcurrentHashMap<String, AuthorizationConfig>(
+                        authorizationConfList), authFileName);
+    }
+
+    @Override
+    public Object readObject(ObjectInputStream ois)
+            throws FileNotFoundException, IOException, ClassNotFoundException {
+        // Perform the class deserialization locally, from inside the package where the class is defined
+        return ois.readObject();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadUserConfig() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<String, UserConfig> confList = (ConcurrentMap<String, UserConfig>) objReader
+                .read(this, usersFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (UserConfig conf : confList.values()) {
+            addLocalUser(conf);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadServerConfig() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<String, ServerConfig> confList = (ConcurrentMap<String, ServerConfig>) objReader
+                .read(this, serversFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (ServerConfig conf : confList.values()) {
+            addAAAServer(conf);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadAuthConfig() {
+        ObjectReader objReader = new ObjectReader();
+        ConcurrentMap<String, AuthorizationConfig> confList = (ConcurrentMap<String, AuthorizationConfig>) objReader
+                .read(this, authFileName);
+
+        if (confList == null) {
+            return;
+        }
+
+        for (AuthorizationConfig conf : confList.values()) {
+            addAuthInfo(conf);
+        }
+    }
+
+    /*
+     * Interaction with GUI START
+     */
+    public Status addRemoveLocalUser(UserConfig AAAconf, boolean delete) {
+        // Validation check
+        if (!AAAconf.isValid()) {
+               String msg = "Invalid Local User configuration";
+            logger.warn(msg);
+            return new Status(StatusCode.BADREQUEST, msg);
+        }
+
+        // Update Config database
+        if (delete) {
+               if (AAAconf.getUser().equals(UserManagerImpl.defaultAdmin)) {
+                       String msg = "Invalid Request: Default Network Admin  User " +
+                                       "cannot be deleted";
+                       logger.debug(msg);
+                       return new Status(StatusCode.NOTALLOWED, msg);
+               }
+            localUserConfigList.remove(AAAconf.getUser());
+        } else {
+               if (AAAconf.getUser().equals(UserManagerImpl.defaultAdmin)) {
+                       String msg = "Invalid Request: Default Network Admin  User " +
+                                       "cannot be added";
+                       logger.debug(msg);
+                       return new Status(StatusCode.NOTALLOWED, msg);
+               }
+            localUserConfigList.put(AAAconf.getUser(), AAAconf);
+        }
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private Status addRemoveAAAServer(ServerConfig AAAconf, boolean delete) {
+        // Validation check
+        if (!AAAconf.isValid()) {
+               String msg = "Invalid Server configuration";
+            logger.warn(msg);
+            return new Status(StatusCode.BADREQUEST, msg);
+        }
+
+        // Update configuration database
+        if (delete) {
+            remoteServerConfigList.remove(AAAconf.getAddress());
+        } else {
+            remoteServerConfigList.put(AAAconf.getAddress(), AAAconf);
+        }
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    private Status addRemoveAuthInfo(AuthorizationConfig AAAconf,
+            boolean delete) {
+        if (!AAAconf.isValid()) {
+               String msg = "Invalid Authorization configuration";
+            logger.warn(msg);
+            return new Status(StatusCode.BADREQUEST, msg);
+        }
+
+        // Update configuration database
+        if (delete) {
+            authorizationConfList.remove(AAAconf.getUser());
+        } else {
+            authorizationConfList.put(AAAconf.getUser(), AAAconf);
+        }
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public Status addLocalUser(UserConfig AAAconf) {
+        return addRemoveLocalUser(AAAconf, false);
+    }
+
+    @Override
+    public Status removeLocalUser(UserConfig AAAconf) {
+        return addRemoveLocalUser(AAAconf, true);
+    }
+
+    @Override
+    public Status removeLocalUser(String userName) {
+       if (userName == null || userName.trim().isEmpty()) {
+               return new Status(StatusCode.BADREQUEST, "Invalid user name");
+       }
+       if (!localUserConfigList.containsKey(userName)) {
+               return new Status(StatusCode.NOTFOUND, "User does not exist");
+       }       
+        return addRemoveLocalUser(localUserConfigList.get(userName), true);
+    }
+    @Override
+    public Status addAAAServer(ServerConfig AAAconf) {
+        return addRemoveAAAServer(AAAconf, false);
+    }
+
+    @Override
+    public Status removeAAAServer(ServerConfig AAAconf) {
+        return addRemoveAAAServer(AAAconf, true);
+    }
+
+    @Override
+    public Status addAuthInfo(AuthorizationConfig AAAconf) {
+        return addRemoveAuthInfo(AAAconf, false);
+    }
+
+    @Override
+    public Status removeAuthInfo(AuthorizationConfig AAAconf) {
+        return addRemoveAuthInfo(AAAconf, true);
+    }
+
+    @Override
+    public List<UserConfig> getLocalUserList() {
+        return new ArrayList<UserConfig>(localUserConfigList.values());
+    }
+
+    @Override
+    public List<ServerConfig> getAAAServerList() {
+        return new ArrayList<ServerConfig>(remoteServerConfigList.values());
+    }
+
+    @Override
+    public List<AuthorizationConfig> getAuthorizationList() {
+        return new ArrayList<AuthorizationConfig>(authorizationConfList
+                .values());
+    }
+
+    @Override
+    public Status changeLocalUserPassword(String user, String curPassword,
+            String newPassword) {
+        UserConfig targetConfigEntry = null;
+
+        // update configuration entry
+        targetConfigEntry = localUserConfigList.get(user);
+        if (targetConfigEntry == null) {
+               return new Status(StatusCode.NOTFOUND, "User not found");
+        }
+        if (false == targetConfigEntry.update(curPassword, newPassword, null)) {
+               return new Status(StatusCode.BADREQUEST, "Current password is incorrect");
+        }
+        localUserConfigList.put(user, targetConfigEntry); // trigger cluster update
+
+        logger.info("Password changed for User \"{}\"", user);
+
+        return new Status(StatusCode.SUCCESS, null);
+    }
+
+    @Override
+    public void userLogout(String userName) {
+        // TODO: if user was authenticated through AAA server, send Acct-Status-Type=stop message to server with logout as reason
+        removeUserFromActiveList(userName);
+        logger.info("User \"{}\" logged out", userName);
+    }
+
+    /*
+     * This function will get called by http session mgr when session times out
+     */
+    @Override
+    public void userTimedOut(String userName) {
+        // TODO: if user was authenticated through AAA server, send Acct-Status-Type=stop message to server with timeout as reason
+        removeUserFromActiveList(userName);
+        logger.info("User \"{}\" timed out", userName);
+    }
+
+    @Override
+    public String getAccessDate(String user) {
+        return this.activeUsers.get(user).getAccessDate();
+    }
+
+    @Override
+    public synchronized Map<String, List<String>> getUserLoggedIn() {
+        Map<String, List<String>> loggedInList = new HashMap<String, List<String>>();
+        for (Map.Entry<String, AuthenticatedUser> user : activeUsers.entrySet()) {
+            String userNameShow = user.getKey();
+            loggedInList.put(userNameShow, user.getValue().getUserRoles());
+        }
+        return loggedInList;
+    }
+
+    /*
+     * Interaction with GUI END
+     */
+
+    /*
+     * Cluster notifications
+     */
+
+    @Override
+    public void entryCreated(Long key, String cacheName, boolean originLocal) {
+        // don't react on this event
+    }
+
+    @Override
+    public void entryUpdated(Long key, String new_value, String cacheName,
+            boolean originLocal) {
+        if (cacheName.equals("localUserSaveConfigEvent")) {
+            this.saveLocalUserListInternal();
+        } else if (cacheName.equals("remoteServerSaveConfigEvent")) {
+            this.saveAAAServerListInternal();
+        } else if (cacheName.equals("authorizationSaveConfigEvent")) {
+            this.saveAuthorizationListInternal();
+        }
+    }
+
+    @Override
+    public void entryDeleted(Long key, String cacheName, boolean originLocal) {
+        // don't react on this event
+    }
+
+    public void _umAddUser(CommandInterpreter ci) {
+        String userName = ci.nextArgument();
+        String password = ci.nextArgument();
+        String role = ci.nextArgument();
+
+        if (userName == null || userName.trim().isEmpty() || password == null
+                || password.trim().isEmpty() || role == null
+                || role.trim().isEmpty()) {
+            ci.println("Invalid Arguments");
+            ci.println("umAddUser <user_name> <password> <user_role>");
+            return;
+        }
+        this.addLocalUser(new UserConfig(userName, password, role));
+    }
+
+    public void _umRemUser(CommandInterpreter ci) {
+        String userName = ci.nextArgument();
+        String password = ci.nextArgument();
+        String role = ci.nextArgument();
+
+        if (userName == null || userName.trim().isEmpty() || password == null
+                || password.trim().isEmpty() || role == null
+                || role.trim().isEmpty()) {
+            ci.println("Invalid Arguments");
+            ci.println("umRemUser <user_name> <password> <user_role>");
+            return;
+        }
+        this.removeLocalUser(new UserConfig(userName, password, role));
+    }
+
+    public void _umGetUsers(CommandInterpreter ci) {
+        for (UserConfig conf : this.getLocalUserList()) {
+            ci.println(conf.getUser() + " " + conf.getRole());
+        }
+    }
+
+    @Override
+    public String getHelp() {
+        StringBuffer help = new StringBuffer();
+        return help.toString();
+    }
+
+    void setClusterGlobalService(IClusterGlobalServices s) {
+        logger.debug("Cluster Service Global set");
+        this.clusterGlobalService = s;
+    }
+
+    void unsetClusterGlobalService(IClusterGlobalServices s) {
+        if (this.clusterGlobalService == s) {
+            logger.debug("Cluster Service Global removed!");
+            this.clusterGlobalService = null;
+        }
+    }
+
+    void unsetContainerAuthClient(IContainerAuthorization s) {
+        if (this.containerAuthorizationClient == s) {
+            this.containerAuthorizationClient = null;
+        }
+    }
+
+    void setContainerAuthClient(IContainerAuthorization s) {
+        this.containerAuthorizationClient = s;
+    }
+
+    void setAppAuthClient(IResourceAuthorization s) {
+        this.applicationAuthorizationClients.add(s);
+    }
+
+    void unsetAppAuthClient(IResourceAuthorization s) {
+        this.applicationAuthorizationClients.remove(s);
+    }
+
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init() {
+    }
+
+    /**
+     * Function called by the dependency manager when at least one
+     * dependency become unsatisfied or when the component is shutting
+     * down because for example bundle is being stopped.
+     *
+     */
+    void destroy() {
+    }
+
+    /**
+     * Function called by dependency manager after "init ()" is called
+     * and after the services provided by the class are registered in
+     * the service registry
+     *
+     */
+    void start() {
+        authProviders = new ConcurrentHashMap<String, IAAAProvider>();
+        // Instantiate cluster synced variables
+        allocateCaches();
+        retrieveCaches();
+
+        // Read startup configuration and populate databases
+        loadConfigurations();
+
+        // Make sure default Network Admin account is there
+        checkDefaultNetworkAdmin();
+        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
+                .getBundleContext();
+        bundleContext.registerService(CommandProvider.class.getName(), this,
+                null);
+    }
+
+    /**
+     * Function called by the dependency manager before the services
+     * exported by the component are unregistered, this will be
+     * followed by a "destroy ()" calls
+     *
+     */
+    void stop() {
+    }
+
+    @Override
+    public List<String> getUserRoles(String userName) {
+        if (userName == null) {
+            return new ArrayList<String>(0);
+        }
+        AuthenticatedUser locatedUser = activeUsers.get(userName);
+        return (locatedUser == null) ? new ArrayList<String>(0) : locatedUser
+                .getUserRoles();
+    }
+
+    @Override
+    public UserLevel getUserLevel(String username) {
+        // Returns the controller well-know user level for the passed user
+        if (!activeUsers.containsKey(username)) {
+            return UserLevel.NOUSER;
+        }
+
+        // For now only one role per user is allowed
+        String roleName = activeUsers.get(username).getUserRoles().get(0);
+        if (roleName.equals(UserLevel.SYSTEMADMIN.toString())) {
+            return UserLevel.SYSTEMADMIN;
+        }
+        if (roleName.equals(UserLevel.NETWORKADMIN.toString())) {
+            return UserLevel.NETWORKADMIN;
+        }
+        if (roleName.equals(UserLevel.NETWORKOPERATOR.toString())) {
+            return UserLevel.NETWORKOPERATOR;
+        }
+        if (this.containerAuthorizationClient != null
+                && this.containerAuthorizationClient
+                        .isApplicationRole(roleName)) {
+            return UserLevel.CONTAINERUSER;
+        }
+        for (IResourceAuthorization client : this.applicationAuthorizationClients) {
+            if (client.isApplicationRole(roleName)) {
+                return UserLevel.APPUSER;
+            }
+        }
+        return UserLevel.NOUSER;
+
+    }
+
+    @Override
+    public Status saveConfiguration() {
+        boolean success = true;
+        Status ret = saveLocalUserList();
+        if (!ret.isSuccess()) {
+            success = false;
+        }
+        ret = saveAAAServerList();
+        if (!ret.isSuccess()) {
+            success = false;
+        }
+        ret = saveAuthorizationList();
+        if (!ret.isSuccess()) {
+            success = false;
+        }
+
+        if (success) {
+            return new Status(StatusCode.SUCCESS, null);
+        }
+
+        return new Status(StatusCode.INTERNALERROR,
+                       "Failed to save user configurations");
+    }
+
+    @Override
+    public UserDetails loadUserByUsername(String username)
+            throws UsernameNotFoundException {
+        AuthenticatedUser user = activeUsers.get(username);
+
+        if (user != null) {
+            boolean enabled = true;
+            boolean accountNonExpired = true;
+            boolean credentialsNonExpired = true;
+            boolean accountNonLocked = true;
+
+            return new User(username, localUserConfigList.get(username)
+                    .getPassword(), enabled, accountNonExpired,
+                    credentialsNonExpired, accountNonLocked, user
+                            .getGrantedAuthorities(getUserLevel(username)));
+        } else
+            throw new UsernameNotFoundException("User not found " + username);
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return UsernamePasswordAuthenticationToken.class
+                .isAssignableFrom(authentication);
+
+    }
+
+    @Override
+    public SecurityContextRepository getSecurityContextRepo() {
+        return securityContextRepo;
+    }
+
+    public void setSecurityContextRepo(
+            SecurityContextRepository securityContextRepo) {
+        this.securityContextRepo = securityContextRepo;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication)
+            throws AuthenticationException {
+
+        if (StringUtils.isBlank((String) authentication.getCredentials())
+                || StringUtils.isBlank((String) authentication.getPrincipal())) {
+            throw new BadCredentialsException(
+                    "Username or credentials did not match");
+        }
+
+        AuthResultEnum result = authenticate((String) authentication
+                .getPrincipal(), (String) authentication.getCredentials());
+        if (result.equals(AuthResultEnum.AUTHOR_PASS)
+                || result.equals(AuthResultEnum.AUTH_ACCEPT_LOC)
+                || result.equals(AuthResultEnum.AUTH_ACCEPT)) {
+
+            AuthenticatedUser user = activeUsers.get(authentication
+                    .getPrincipal().toString());
+
+            if (user == null) {
+                throw new AuthenticationServiceException(
+                        "Authentication Failure");
+            }
+
+            authentication = new UsernamePasswordAuthenticationToken(
+                    authentication.getPrincipal(), authentication
+                            .getCredentials(), user
+                            .getGrantedAuthorities(getUserLevel(authentication
+                                    .getName())));
+            return authentication;
+
+        } else
+            throw new BadCredentialsException(
+                    "Username or credentials did not match");
+
+    }
+
+    //following are setters for use in unit testing
+    void setLocalUserConfigList(ConcurrentMap<String, UserConfig> ucl) {
+       if (ucl != null) { this.localUserConfigList = ucl; }
+    }
+    void setRemoteServerConfigList (ConcurrentMap<String, ServerConfig> scl) {
+       if (scl != null) { this.remoteServerConfigList = scl; }
+    }
+    void setAuthorizationConfList (ConcurrentMap<String, AuthorizationConfig> acl) {
+       if (acl != null) { this.authorizationConfList = acl; }
+    }
+    void setActiveUsers (ConcurrentMap<String, AuthenticatedUser> au) {
+        if (au != null) { this.activeUsers = au; }
+    }
+    void setAuthProviders(ConcurrentMap<String, IAAAProvider> ap ) {
+        if (ap != null){ 
+            this.authProviders = ap;
+        }
+    }
+    
+    @Override
+    public ISessionManager getSessionManager() {
+        return this.sessionMgr;
+    }
+    
+    public void setSessionMgr(ISessionManager sessionMgr) {
+        this.sessionMgr = sessionMgr;
+    }
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/security/SessionManager.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/security/SessionManager.java
new file mode 100644 (file)
index 0000000..bbad9eb
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.security;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.opendaylight.controller.usermanager.ISessionManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContext;
+
+public class SessionManager implements ISessionManager {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(SessionManager.class);
+
+    private Map<ServletContext, Set<HttpSession>> sessionMap = new HashMap<ServletContext, Set<HttpSession>>();
+    public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
+
+    @Override
+    public void sessionCreated(HttpSessionEvent se) {
+
+        ServletContext ctx = se.getSession().getServletContext();
+        String path = ctx.getContextPath();
+
+        logger.debug("Servlet Context Path created " + path);
+        logger.debug("Session Id created for ctxt path " + se.getSession().getId());
+
+        synchronized (sessionMap) {
+            Set<HttpSession> set = sessionMap.get(ctx);
+            if (set == null) {
+                set = new HashSet<HttpSession>();
+                sessionMap.put(ctx, set);
+            }
+            set.add(se.getSession());
+        }
+    }
+
+    @Override
+    public void sessionDestroyed(HttpSessionEvent se) {
+        ServletContext ctx = se.getSession().getServletContext();
+        String path = ctx.getContextPath();
+        logger.debug("Servlet Context Path of destroyed session - " + path);
+        logger.debug("Session Id destroyed " + se.getSession().getId());
+
+        synchronized (sessionMap) {
+            Set<HttpSession> set = sessionMap.get(ctx);
+            if (set != null) {
+                set.remove(se.getSession());
+            }
+        }
+    }
+
+    @Override
+    public void invalidateSessions(String username, String sessionId) {
+
+        synchronized (sessionMap) {
+            List<HttpSession> sessionsList = new ArrayList<HttpSession>();
+            Iterator<Map.Entry<ServletContext, Set<HttpSession>>> sessMapIterator = sessionMap
+                    .entrySet().iterator();
+            while (sessMapIterator.hasNext()) {
+
+                Entry<ServletContext, Set<HttpSession>> val = sessMapIterator
+                        .next();
+                Iterator<HttpSession> sessIterator = val.getValue().iterator();
+
+                while (sessIterator.hasNext()) {
+                    HttpSession session = sessIterator.next();
+                    if (session != null && sessionId != null && session.getId() != null && !session.getId().equals(sessionId)) {
+                        Object contextFromSession = session
+                                .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
+                        if (contextFromSession != null
+                                && contextFromSession instanceof SecurityContext) {
+                            String storedUserName = ((SecurityContext) contextFromSession)
+                                    .getAuthentication().getName();
+                            if (storedUserName != null && storedUserName.equals(username)) {                                
+                                sessionsList.add(session);                                
+                                sessIterator.remove();
+                            }
+                            else {
+                                logger.debug("storedUserName is null or did not match username " + username);
+                            }
+                        } else {
+                            logger.debug("contextFromSession is null or not instance of SecurityContext");
+                        }
+                    }
+                    else {
+                        logger.debug(" session or sessionId is null ");
+                    }
+                }
+            }
+
+            Iterator<HttpSession> sessionIt = sessionsList.iterator();
+            while (sessionIt.hasNext()) {
+                HttpSession session = sessionIt.next();
+                sessionIt.remove();
+                session.invalidate();
+            }
+        }
+    }
+
+}
diff --git a/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/security/UserSecurityContextRepository.java b/opendaylight/usermanager/src/main/java/org/opendaylight/controller/usermanager/security/UserSecurityContextRepository.java
new file mode 100644 (file)
index 0000000..655f77d
--- /dev/null
@@ -0,0 +1,42 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.security;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.web.context.HttpRequestResponseHolder;
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+public class UserSecurityContextRepository implements
+        SecurityContextRepository {
+
+    HttpSessionSecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
+
+    @Override
+    public SecurityContext loadContext(
+            HttpRequestResponseHolder requestResponseHolder) {
+        return securityContextRepository.loadContext(requestResponseHolder);
+    }
+
+    @Override
+    public void saveContext(SecurityContext context,
+            HttpServletRequest request, HttpServletResponse response) {
+        securityContextRepository.saveContext(context, request, response);
+    }
+
+    @Override
+    public boolean containsContext(HttpServletRequest request) {
+        return securityContextRepository.containsContext(request);
+    }
+
+}
diff --git a/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/AuthResponseTest.java b/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/AuthResponseTest.java
new file mode 100644 (file)
index 0000000..cb4c75f
--- /dev/null
@@ -0,0 +1,14 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager;
+
+public class AuthResponseTest{
+       
+}
\ No newline at end of file
diff --git a/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/AuthenticatedUserTest.java b/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/AuthenticatedUserTest.java
new file mode 100644 (file)
index 0000000..4a379cd
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.springframework.security.core.GrantedAuthority;
+
+public class AuthenticatedUserTest {
+
+       static String[] roleArray;
+       static AuthenticatedUser user;
+
+       @BeforeClass
+       public static void testSetup() {
+               roleArray = new String[] { UserLevel.NETWORKOPERATOR.toString(),
+                               UserLevel.APPUSER.toString() };
+       }
+
+       @Test
+       public void testAuthenticatedUser() {
+               user = new AuthenticatedUser("auser");
+
+               Assert.assertFalse(user.getAccessDate().isEmpty());
+               Assert.assertNull(user.getUserRoles());
+
+       }
+
+       @Test
+       public void testSetUserRoleList() {
+               List<String> retrievedRoleList = null;
+               List<String> roleList = Arrays.asList(roleArray);
+
+               // list arg
+               user = new AuthenticatedUser("auser");
+               user.setRoleList(roleList);
+               retrievedRoleList = user.getUserRoles();
+               Assert.assertTrue(roleList.equals(retrievedRoleList));
+
+               // array arg
+               user = new AuthenticatedUser("auser");
+               user.setRoleList(roleArray);
+               retrievedRoleList = user.getUserRoles();
+               for (int i = 0; i < roleArray.length; i++)
+                       Assert.assertTrue(roleArray[i].equals(retrievedRoleList.get(i)));
+
+               // test addUserRole
+               user.addUserRole("AnotherRole");
+               Assert.assertTrue(user.getUserRoles().lastIndexOf("AnotherRole") != -1);
+
+       }
+
+       @Test
+       public void testGetGrantedAuthorities() {
+               List<GrantedAuthority> gaList = user
+                               .getGrantedAuthorities(UserLevel.NETWORKOPERATOR);
+               Assert.assertTrue(gaList.get(0).getAuthority()
+                               .equals("ROLE_NETWORK-OPERATOR"));
+       }
+
+}
diff --git a/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/AuthorizationUserConfigTest.java b/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/AuthorizationUserConfigTest.java
new file mode 100644 (file)
index 0000000..12c7690
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.usermanager.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.opendaylight.controller.sal.authorization.AuthResultEnum;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.usermanager.AuthResponse;
+
+/*
+ * This test case includes tests for UserConfig and the extending class AuthorizationConfig
+ */
+public class AuthorizationUserConfigTest {
+
+       @Test
+       public void AuthorizationConfigTest() {
+               AuthorizationConfig authConfig;
+
+               // test isValid
+               authConfig = new AuthorizationConfig(null,
+                               UserLevel.SYSTEMADMIN.toString());
+               assertFalse(authConfig.isValid());
+               authConfig = new AuthorizationConfig("admin", "");
+               assertFalse(authConfig.isValid());
+               authConfig = new AuthorizationConfig("admin",
+                               UserLevel.SYSTEMADMIN.toString());
+               assertTrue(authConfig.isValid());               
+       }
+
+       @Test
+       public void UserConfigTest() {
+               UserConfig userConfig;
+
+               userConfig = new UserConfig(null, "cisco",
+                               UserLevel.NETWORKOPERATOR.toString());
+               assertFalse(userConfig.isValid());
+
+               userConfig = new UserConfig("uname", "", "cisco");
+               assertFalse(userConfig.isValid());
+
+               userConfig = new UserConfig("uname", "ciscocisco",
+                               UserLevel.NETWORKOPERATOR.toString());
+               assertTrue(userConfig.isValid());
+
+               /* currentPassword mismatch */
+               assertFalse(userConfig.update("Cisco", "cisco123",
+                               UserLevel.NETWORKOPERATOR.toString()));
+
+               assertTrue(userConfig.update("ciscocisco", null,
+                               UserLevel.NETWORKOPERATOR.toString()));
+               /* New Password = null, No change in password */
+               assertTrue(userConfig.getPassword().equals("ciscocisco"));
+
+               /* Password changed successfully, no change in user role */
+               assertTrue(userConfig.update("ciscocisco", "cisco123",
+                               UserLevel.NETWORKOPERATOR.toString()));
+               assertTrue(userConfig.getPassword().equals("cisco123"));
+               assertTrue(userConfig.getRole().equals(
+                               UserLevel.NETWORKOPERATOR.toString()));
+
+               /* Password not changed, role changed successfully */
+               assertTrue(userConfig.update("cisco123", "cisco123",
+                               UserLevel.SYSTEMADMIN.toString()));
+               assertTrue(userConfig.getPassword().equals("cisco123"));
+               assertTrue(userConfig.getRole()
+                               .equals(UserLevel.SYSTEMADMIN.toString()));
+
+               /* Password and role changed successfully */
+               assertTrue(userConfig.update("cisco123", "ciscocisco",
+                               UserLevel.SYSTEMADMIN.toString()));
+               assertTrue(userConfig.getPassword().equals("ciscocisco"));
+               assertTrue(userConfig.getRole()
+                               .equals(UserLevel.SYSTEMADMIN.toString()));
+
+               String username = userConfig.getUser();
+               assertTrue(username.equals("uname"));
+
+               // test authenticate
+               AuthResponse authresp = userConfig.authenticate("ciscocisco");
+               assertTrue(authresp.getStatus().equals(AuthResultEnum.AUTH_ACCEPT_LOC));
+               authresp = userConfig.authenticate("wrongPassword");
+               assertTrue(authresp.getStatus().equals(AuthResultEnum.AUTH_REJECT_LOC));
+
+               // test equals()
+               userConfig = new UserConfig("uname", "ciscocisco",
+                               UserLevel.NETWORKOPERATOR.toString());
+               assertEquals(userConfig, userConfig);
+               UserConfig userConfig2 = new UserConfig("uname",
+                               "ciscocisco",
+                               UserLevel.NETWORKOPERATOR.toString());
+               assertEquals(userConfig, userConfig2);
+       }
+}
diff --git a/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/ServerConfigTest.java b/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/ServerConfigTest.java
new file mode 100644 (file)
index 0000000..84ddc89
--- /dev/null
@@ -0,0 +1,4 @@
+package org.opendaylight.controller.usermanager.internal;
+
+public class ServerConfigTest   {
+}
diff --git a/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/UserManagerImplTest.java b/opendaylight/usermanager/src/test/java/org/opendaylight/controller/usermanager/internal/UserManagerImplTest.java
new file mode 100644 (file)
index 0000000..5b65028
--- /dev/null
@@ -0,0 +1,231 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.usermanager.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.sal.authorization.AuthResultEnum;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.AuthResponse;
+import org.opendaylight.controller.usermanager.IAAAProvider;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+/**
+ * Unit Tests for UserManagerImpl
+ */
+public class UserManagerImplTest {
+
+       private static UserManagerImpl um;
+
+       /**
+        * @throws java.lang.Exception
+        */
+       @BeforeClass
+       public static void setUpBeforeClass() throws Exception {
+
+               IUserManager userManager = (IUserManager) ServiceHelper
+                               .getGlobalInstance(IUserManager.class, new Object());
+               if (userManager instanceof UserManagerImpl) {
+                       um = (UserManagerImpl) userManager;
+               } else {
+                       um = new UserManagerImpl();
+                       um.setAuthProviders(new ConcurrentHashMap<String, IAAAProvider>());
+
+                       // mock up a remote server list with a dummy server
+                       um.setRemoteServerConfigList(new ConcurrentHashMap<String, ServerConfig>() {
+                               static final long serialVersionUID = 1L;
+                               {
+                                       put("dummyServerConfig", new ServerConfig() { // Server config can't be empty
+                                                               static final long serialVersionUID = 8645L;
+
+                                                               public String getAddress() {
+                                                                       return "1.1.1.1";
+                                                               }
+
+                                                               public String getSecret() {
+                                                                       return "secret";
+                                                               }
+
+                                                               public String getProtocol() {
+                                                                       return "IPv4";
+                                                               }
+                                                       });
+                               }
+                       });
+
+                       // mock up a localUserConfigList with an admin user
+                       um.setLocalUserConfigList(new ConcurrentHashMap<String, UserConfig>() {
+                               static final long serialVersionUID = 2L;
+                               {
+                                       put("admin", new UserConfig("admin", "7029,7455,8165,7029,7881",
+                                                                       UserLevel.SYSTEMADMIN.toString()));
+                               }
+                       });
+                       // instantiate an empty activeUser collection
+                       um.setActiveUsers(new ConcurrentHashMap<String, AuthenticatedUser>());
+
+               }
+
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#addAAAProvider(org.opendaylight.controller.usermanager.IAAAProvider)}
+        * .
+        */
+       @Test
+       public void testAddAAAProvider() {
+               // instantiate an anonymous AAAProvider
+               IAAAProvider a3p = new IAAAProvider() {
+
+                       public AuthResponse authService(String userName, String password,
+                                       String server, String secretKey) {
+                               return new AuthResponse();
+                       };
+
+                       public String getName() {
+                               return "dummyAAAProvider";
+                       }
+               };
+
+               um.addAAAProvider(a3p);
+               assertEquals(a3p, um.getAAAProvider("dummyAAAProvider"));
+
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#removeAAAProvider(org.opendaylight.controller.usermanager.IAAAProvider)}
+        * and for for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#getAAAProvider(java.lang.String)}
+        * .
+        */
+       @Test
+       public void testRemoveAAAProvider() {
+               um.removeAAAProvider(um.getAAAProvider("dummyAAAProvider"));
+               assertTrue(um.getAAAProviderNames().isEmpty());
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#authenticate(java.lang.String, java.lang.String)}
+        * .
+        */
+       @Test
+       public void testAuthenticateStringString() {
+               UserConfig uc = new UserConfig("administrator", "admin",
+                               UserLevel.SYSTEMADMIN.toString());
+               um.addLocalUser(uc);
+               AuthResultEnum authResult = um.authenticate("administrator", "admin");
+               assertEquals(authResult, AuthResultEnum.AUTH_ACCEPT_LOC);
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#addRemoveLocalUser(org.opendaylight.controller.usermanager.internal.UserConfig, boolean)}
+        * .
+        */
+       @Test
+       public void testAddRemoveLocalUser() {
+               UserConfig uc = new UserConfig("sysadmin", "7029,7455,8165,7029,7881",
+                               UserLevel.SYSTEMADMIN.toString());
+               um.addLocalUser(uc);
+               assertTrue(um.getLocalUserList().contains(uc));
+               um.removeLocalUser(uc);
+               assertFalse(um.getLocalUserList().contains(uc));
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#changeLocalUserPassword(java.lang.String, java.lang.String, java.lang.String)}
+        * .
+        */
+       @Test
+       public void testChangeLocalUserPassword() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#userLogout(java.lang.String)}
+        * .
+        */
+       @Test
+       public void testUserLogout() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#userTimedOut(java.lang.String)}
+        * .
+        */
+       @Test
+       public void testUserTimedOut() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#authenticate(org.springframework.security.core.Authentication)}
+        * .
+        */
+       @Test
+       public void testAuthenticateAuthentication() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#saveLocalUserList()}
+        * .
+        */
+       @Test
+       public void testSaveLocalUserList() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#saveAAAServerList()}
+        * .
+        */
+       @Test
+       public void testSaveAAAServerList() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#saveAuthorizationList()}
+        * .
+        */
+       @Test
+       public void testSaveAuthorizationList() {
+               // fail("Not yet implemented");
+       }
+
+       /**
+        * Test method for
+        * {@link org.opendaylight.controller.usermanager.internal.UserManagerImpl#readObject(java.io.ObjectInputStream)}
+        * .
+        */
+       @Test
+       public void testReadObject() {
+               // fail("Not yet implemented");
+       }
+}
diff --git a/opendaylight/web/devices/pom.xml b/opendaylight/web/devices/pom.xml
new file mode 100644 (file)
index 0000000..00360c3
--- /dev/null
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../../commons/opendaylight</relativePath>
+       </parent>
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>devices.web</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Export-Package>
+                                               </Export-Package>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.forwardingrulesmanager,
+                                                       org.opendaylight.controller.sal.authorization,
+                                                       org.opendaylight.controller.sal.action,
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.containermanager,
+                                                       org.opendaylight.controller.switchmanager,
+                                                       org.opendaylight.controller.forwarding.staticrouting,
+                                                       org.opendaylight.controller.usermanager,
+                                                       org.opendaylight.controller.web,
+                                                       com.google.gson,
+                                                       javax.annotation,
+                                                       javax.naming,
+                                                       javax.servlet,
+                                                       javax.servlet.annotation,
+                                                       javax.servlet.http,
+                                                       javax.servlet.jsp,
+                                                       javax.servlet.jsp.el,
+                                                       javax.servlet.jsp.jstl.core,
+                                                       javax.servlet.jsp.jstl.fmt,
+                                                       javax.servlet.jsp.jstl.tlv,
+                                                       javax.servlet.jsp.tagext,
+                                                       javax.servlet.resources,
+                                                       javax.xml.parsers,
+                                                       javax.xml.transform,
+                                                       org.apache.commons.logging,
+                                                       org.apache.taglibs.standard.functions,
+                                                       org.apache.taglibs.standard.resources,
+                                                       org.apache.taglibs.standard.tag.common.core,
+                                                       org.apache.taglibs.standard.tag.common.fmt,
+                                                       org.apache.taglibs.standard.tag.rt.core,
+                                                       org.apache.taglibs.standard.tag.rt.fmt,
+                                                       org.apache.taglibs.standard.tei,
+                                                       org.apache.taglibs.standard.tlv,
+                                                       org.codehaus.jackson,
+                                                       org.codehaus.jackson.annotate,
+                                                       org.codehaus.jackson.map,
+                                                       org.codehaus.jackson.map.annotate,
+                                                       org.osgi.framework,
+                                                       org.slf4j,
+                                                       org.springframework.beans,
+                                                       org.springframework.beans.factory.xml,
+                                                       org.springframework.context.config,
+                                                       org.springframework.stereotype,
+                                                       org.springframework.web,
+                                                       org.springframework.web.bind.annotation,
+                                                       org.springframework.web.servlet,
+                                                       org.springframework.web.servlet.config,
+                                                       org.springframework.web.servlet.view,
+
+                                                       org.springframework.web.filter,
+                                                       org.springframework.web.context,
+                                                       org.springframework.security.core,
+                                                       org.springframework.security.core.userdetails,
+                                                       org.springframework.security.core.authority,
+                                                       org.springframework.security.core.context,
+                                                       org.springframework.security.authentication,
+                                                       org.springframework.security.config,
+                                                       org.springframework.security.config.authentication,
+                                                       org.springframework.security.taglibs.authz,
+                                                       org.springframework.security.web,
+                                                       org.springframework.security.web.context,
+                                                       org.springframework.security.web.authentication,
+                                                       org.springframework.security.web.authentication.www,
+                                                       org.springframework.security.provisioning,
+                                                       org.springframework.security.web.util,
+                                                       org.springframework.security.web.authentication.rememberme,
+                                                       org.springframework.security.web.authentication.logout,
+                                                       org.springframework.dao
+                                               </Import-Package>
+                                               <Web-ContextPath>/one/devices</Web-ContextPath>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>forwardingrulesmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>containermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>switchmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>web</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>forwarding.staticrouting</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/Devices.java
new file mode 100644 (file)
index 0000000..77b3e13
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.devices.web;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.web.IOneWeb;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.opendaylight.controller.forwarding.staticrouting.IForwardingStaticRouting;
+import org.opendaylight.controller.forwarding.staticrouting.StaticRouteConfig;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Tier;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.TierHelper;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.SpanConfig;
+import org.opendaylight.controller.switchmanager.SubnetConfig;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+
+import com.google.gson.Gson;
+
+@Controller
+@RequestMapping("/")
+public class Devices implements IOneWeb {
+    private static final UserLevel AUTH_LEVEL = UserLevel.CONTAINERUSER;
+    private final String WEB_NAME = "Devices";
+    private final String WEB_ID = "devices";
+    private final short WEB_ORDER = 1;
+    private final String containerName = GlobalConstants.DEFAULT.toString();
+
+    public Devices() {
+        ServiceHelper.registerGlobalService(IOneWeb.class, this, null);
+    }
+
+    @Override
+    public String getWebName() {
+        return WEB_NAME;
+    }
+
+    @Override
+    public String getWebId() {
+        return WEB_ID;
+    }
+
+    @Override
+    public short getWebOrder() {
+        return WEB_ORDER;
+    }
+
+    @Override
+    public boolean isAuthorized(UserLevel userLevel) {
+        return userLevel.ordinal() <= AUTH_LEVEL.ordinal();
+    }
+
+    @RequestMapping(value = "/nodesLearnt", method = RequestMethod.GET)
+    @ResponseBody
+    public DevicesJsonBean getNodesLearnt() {
+        Gson gson = new Gson();
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        List<Map<String, String>> nodeData = new ArrayList<Map<String, String>>();
+        for (Switch device : switchManager.getNetworkDevices()) {
+            HashMap<String, String> nodeDatum = new HashMap<String, String>();
+            Node node = device.getNode();
+            SwitchConfig switchConfig = switchManager.getSwitchConfig(node.getNodeIDString());
+            Tier tier = (Tier) switchManager.getNodeProp(node,
+                    Tier.TierPropName);
+            String swName = switchConfig == null ? null : switchConfig.getNodeName();
+            nodeDatum.put("containerName", containerName);
+            nodeDatum.put("nodeName", swName);
+            nodeDatum.put("nodeId", node.getNodeIDString());
+            int tierNumber = (tier == null) ? TierHelper.unknownTierNumber
+                    : tier.getValue();
+            nodeDatum.put("tierName", TierHelper.getTierName(tierNumber)
+                    + " (Tier-" + tierNumber + ")");
+            nodeDatum.put("tier", tierNumber + "");
+            SwitchConfig sc = switchManager.getSwitchConfig(device.getNode()
+                    .getNodeIDString());
+            String modeStr = (sc != null) ? sc.getMode() : "0";
+            nodeDatum.put("mode", modeStr);
+
+            nodeDatum.put("json", gson.toJson(nodeDatum));
+            nodeDatum.put("mac",
+                    HexEncode.bytesToHexString(device.getDataLayerAddress()));
+            StringBuffer sb1 = new StringBuffer();
+            Set<NodeConnector> nodeConnectorSet = device.getNodeConnectors();
+            String nodeConnectorName;
+            String nodeConnectorNumberToStr;
+            if (nodeConnectorSet != null && nodeConnectorSet.size() > 0) {
+                Map<Short, String> portList = new HashMap<Short, String>();
+                for (NodeConnector nodeConnector : nodeConnectorSet) {
+                    nodeConnectorNumberToStr = nodeConnector.getID().toString();
+                    Name ncName = ((Name) switchManager
+                            .getNodeConnectorProp(nodeConnector,
+                                    Name.NamePropName));
+                    nodeConnectorName = (ncName != null) ? ncName.getValue() : "";
+                    portList.put(Short.parseShort(nodeConnectorNumberToStr),
+                            nodeConnectorName);
+                }
+                Map<Short, String> sortedPortList = new TreeMap<Short, String>(
+                        portList);
+                for (Entry<Short, String> e : sortedPortList.entrySet()) {
+                    sb1.append(e.getValue() + "(" + e.getKey() + ")");
+                    sb1.append("<br>");
+                }
+            }
+            nodeDatum.put("ports", sb1.toString());
+            nodeData.add(nodeDatum);
+        }
+        DevicesJsonBean result = new DevicesJsonBean();
+        result.setNodeData(nodeData);
+        List<String> columnNames = new ArrayList<String>();
+        columnNames.add("Node ID");
+        columnNames.add("Node Name");
+        columnNames.add("Tier");
+        columnNames.add("Mac Address");
+        columnNames.add("Ports");
+        
+        result.setColumnNames(columnNames);
+        return result;
+    }
+
+    @RequestMapping(value = "/tiers", method = RequestMethod.GET)
+    @ResponseBody
+    public List<String> getTiers() {
+        return TierHelper.getTiers();
+    }
+    
+    @RequestMapping(value = "/nodesLearnt/update", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean updateLearntNode(
+            @RequestParam("nodeName") String nodeName,
+            @RequestParam("nodeId") String nodeId,
+            @RequestParam("tier") String tier,
+            @RequestParam("operationMode") String operationMode) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+       StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            SwitchConfig cfg = new SwitchConfig(nodeId, nodeName, tier,
+                    operationMode);
+            switchManager.updateSwitchConfig(cfg);
+            resultBean.setStatus(true);
+            resultBean.setMessage("Updated node information successfully");
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage("Error updating node information. "
+                    + e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/staticRoutes", method = RequestMethod.GET)
+    @ResponseBody
+    public DevicesJsonBean getStaticRoutes() {
+        Gson gson = new Gson();
+        IForwardingStaticRouting staticRouting = (IForwardingStaticRouting) ServiceHelper
+                .getInstance(IForwardingStaticRouting.class, containerName, this);
+        List<Map<String, String>> staticRoutes = new ArrayList<Map<String, String>>();
+        ConcurrentMap<String, StaticRouteConfig> routeConfigs = staticRouting
+                .getStaticRouteConfigs();
+        if (routeConfigs == null) {
+            return null;
+        }
+        for (StaticRouteConfig conf : routeConfigs.values()) {
+            Map<String, String> staticRoute = new HashMap<String, String>();
+            staticRoute.put("name", conf.getName());
+            staticRoute.put("staticRoute", conf.getStaticRoute());
+            staticRoute.put("nextHopType", conf.getNextHopType());
+            staticRoute.put("nextHop", conf.getNextHop());
+            staticRoute.put("json", gson.toJson(conf));
+            staticRoutes.add(staticRoute);
+        }
+        DevicesJsonBean result = new DevicesJsonBean();
+        result.setColumnNames(StaticRouteConfig.getGuiFieldsNames());
+        result.setNodeData(staticRoutes);
+        return result;
+    }
+
+    @RequestMapping(value = "/staticRoute/add", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean addStaticRoute(
+            @RequestParam("routeName") String routeName,
+            @RequestParam("staticRoute") String staticRoute,
+            @RequestParam("nextHop") String nextHop) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean result = new StatusJsonBean();
+        try {
+            IForwardingStaticRouting staticRouting = (IForwardingStaticRouting) ServiceHelper
+                    .getInstance(IForwardingStaticRouting.class, containerName,
+                            this);
+            StaticRouteConfig config = new StaticRouteConfig();
+            config.setName(routeName);
+            config.setStaticRoute(staticRoute);
+            config.setNextHop(nextHop);
+            Status addStaticRouteResult = staticRouting.addStaticRoute(config);
+            if (addStaticRouteResult.isSuccess()) {
+                result.setStatus(true);
+                result.setMessage("Static Route saved successfully");
+            } else {
+                result.setStatus(false);
+                result.setMessage(addStaticRouteResult.getDescription());
+            }
+        } catch (Exception e) {
+            result.setStatus(false);
+            result.setMessage("Error - " + e.getMessage());
+        }
+        return result;
+    }
+
+    @RequestMapping(value = "/staticRoute/delete", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean deleteStaticRoute(
+            @RequestParam("routesToDelete") String routesToDelete) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            IForwardingStaticRouting staticRouting = (IForwardingStaticRouting) ServiceHelper
+                    .getInstance(IForwardingStaticRouting.class, containerName,
+                            this);
+            String[] routes = routesToDelete.split(",");
+            Status result;
+            resultBean.setStatus(true);
+            resultBean
+                    .setMessage("Successfully deleted selected static routes");
+            for (String route : routes) {
+                result = staticRouting.removeStaticRoute(route);
+                if (!result.isSuccess()) {
+                    resultBean.setStatus(false);
+                    resultBean.setMessage(result.getDescription());
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean
+                    .setMessage("Error occurred while deleting static routes. "
+                            + e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/subnets", method = RequestMethod.GET)
+    @ResponseBody
+    public DevicesJsonBean getSubnetGateways() {
+        Gson gson = new Gson();
+        List<Map<String, String>> subnets = new ArrayList<Map<String, String>>();
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        for (SubnetConfig conf : switchManager.getSubnetsConfigList()) {
+            Map<String, String> subnet = new HashMap<String, String>();
+            subnet.put("name", conf.getName());
+            subnet.put("subnet", conf.getSubnet());
+            subnet.put("json", gson.toJson(conf));
+            subnets.add(subnet);
+        }
+        DevicesJsonBean result = new DevicesJsonBean();
+        result.setColumnNames(SubnetConfig.getGuiFieldsNames());
+        result.setNodeData(subnets);
+        return result;
+    }
+    
+    @RequestMapping(value = "/subnetGateway/add", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean addSubnetGateways(
+            @RequestParam("gatewayName") String gatewayName,
+            @RequestParam("gatewayIPAddress") String gatewayIPAddress) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            SubnetConfig cfgObject = new SubnetConfig(gatewayName,
+                    gatewayIPAddress, new ArrayList<String>());
+            Status result = switchManager.addSubnet(cfgObject);
+            if (result.isSuccess()) {
+                resultBean.setStatus(true);
+                resultBean.setMessage("Added gateway address successfully");
+            } else {
+                resultBean.setStatus(false);
+                resultBean.setMessage(result.getDescription());
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage(e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/subnetGateway/delete", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean deleteSubnetGateways(
+            @RequestParam("gatewaysToDelete") String gatewaysToDelete) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            String[] subnets = gatewaysToDelete.split(",");
+            resultBean.setStatus(true);
+            resultBean.setMessage("Added gateway address successfully");
+            for (String subnet : subnets) {
+                Status result = switchManager.removeSubnet(subnet);
+                if (!result.isSuccess()) {
+                    resultBean.setStatus(false);
+                    resultBean.setMessage(result.getDescription());
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage(e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/subnetGateway/ports/add", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean addSubnetGatewayPort(
+            @RequestParam("portsName") String portsName,
+            @RequestParam("ports") String ports,
+            @RequestParam("nodeId") String nodeId) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            Status result = switchManager.addPortsToSubnet(portsName, nodeId
+                    + "/" + ports);
+
+            if (result.isSuccess()) {
+                resultBean.setStatus(true);
+                resultBean
+                        .setMessage("Added ports to subnet gateway address successfully");
+            } else {
+                resultBean.setStatus(false);
+                resultBean.setMessage(result.getDescription());
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage(e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/subnetGateway/ports/delete", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean deleteSubnetGatewayPort(
+            @RequestParam("gatewayName") String gatewayName,
+            @RequestParam("nodePort") String nodePort) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            Status result = switchManager.removePortsFromSubnet(gatewayName,
+                    nodePort);
+
+            if (result.isSuccess()) {
+                resultBean.setStatus(true);
+                resultBean
+                        .setMessage("Deleted port from subnet gateway address successfully");
+            } else {
+                resultBean.setStatus(false);
+                resultBean.setMessage(result.getDescription());
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage(e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/spanPorts", method = RequestMethod.GET)
+    @ResponseBody
+    public DevicesJsonBean getSpanPorts() {
+        Gson gson = new Gson();
+        List<String> spanConfigs_json = new ArrayList<String>();
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        for (SpanConfig conf : switchManager.getSpanConfigList()) {
+            spanConfigs_json.add(gson.toJson(conf));
+        }
+        ObjectMapper mapper = new ObjectMapper();
+        List<Map<String, String>> spanConfigs = new ArrayList<Map<String, String>>();
+        for (String config_json : spanConfigs_json) {
+            try {
+                @SuppressWarnings("unchecked")
+                Map<String, String> config_data = mapper.readValue(config_json,
+                        HashMap.class);
+                Map<String, String> config = new HashMap<String, String>();
+                for (String name : config_data.keySet()) {
+                    config.put(name, config_data.get(name));
+                    // Add switch name value (non-configuration field)
+                    config.put("nodeName",
+                            getNodeName(config_data.get("nodeId")));
+                }
+                config.put("json", config_json);
+                spanConfigs.add(config);
+            } catch (Exception e) {
+                // TODO: Handle the exception.
+            }
+        }
+        DevicesJsonBean result = new DevicesJsonBean();
+        result.setColumnNames(SpanConfig.getGuiFieldsNames());
+        result.setNodeData(spanConfigs);
+        return result;
+    }
+
+    @RequestMapping(value = "/nodeports")
+    @ResponseBody
+    public Map<String, Object> getNodePorts() {
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        if (switchManager == null)
+            return null;
+
+        Map<String, Object> nodes = new HashMap<String, Object>();
+        Map<Short, String> port;
+
+        for (Switch node : switchManager.getNetworkDevices()) {
+            port = new HashMap<Short, String>(); // new port
+            Set<NodeConnector> nodeConnectorSet = node.getNodeConnectors();
+
+            if (nodeConnectorSet != null)
+                for (NodeConnector nodeConnector : nodeConnectorSet) {
+                    String nodeConnectorName = ((Name) switchManager
+                            .getNodeConnectorProp(nodeConnector,
+                                    Name.NamePropName)).getValue();
+                    port.put((Short) nodeConnector.getID(), nodeConnectorName
+                            + "(" + nodeConnector.getID() + ")");
+                }
+
+            nodes.put(node.getNode().toString(), port);
+        }
+
+        return nodes;
+    }
+
+    @RequestMapping(value = "/spanPorts/add", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean addSpanPort(@RequestParam("jsonData") String jsonData) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            Gson gson = new Gson();
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            SpanConfig cfgObject = gson.fromJson(jsonData, SpanConfig.class);
+            Status result = switchManager.addSpanConfig(cfgObject);
+            if (result.isSuccess()) {
+                resultBean.setStatus(true);
+                resultBean.setMessage("SPAN Port added successfully");
+            } else {
+                resultBean.setStatus(false);
+                resultBean.setMessage(result.getDescription());
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage("Error occurred while adding span port. "
+                    + e.getMessage());
+        }
+        return resultBean;
+    }
+
+    @RequestMapping(value = "/spanPorts/delete", method = RequestMethod.GET)
+    @ResponseBody
+    public StatusJsonBean deleteSpanPorts(
+            @RequestParam("spanPortsToDelete") String spanPortsToDelete) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return unauthorizedMessage();
+       }
+       
+        StatusJsonBean resultBean = new StatusJsonBean();
+        try {
+            Gson gson = new Gson();
+            ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                    .getInstance(ISwitchManager.class, containerName, this);
+            String[] spans = spanPortsToDelete.split("###");
+            resultBean.setStatus(true);
+            resultBean.setMessage("SPAN Port(s) deleted successfully");
+            for (String span : spans) {
+                if (!span.isEmpty()) {
+                    SpanConfig cfgObject = gson
+                            .fromJson(span, SpanConfig.class);
+                    Status result = switchManager.removeSpanConfig(cfgObject);
+                    if (!result.isSuccess()) {
+                        resultBean.setStatus(false);
+                        resultBean.setMessage(result.getDescription());
+                        break;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            resultBean.setStatus(false);
+            resultBean.setMessage("Error occurred while deleting span port. "
+                    + e.getMessage());
+        }
+        return resultBean;
+    }
+
+    private String getNodeName(String nodeId) {
+        String nodeName = nodeId;
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+
+        Node node = Node.fromString(nodeId);
+        if (switchManager != null) {
+            SwitchConfig config = switchManager.getSwitchConfig(node
+                    .getNodeIDString());
+            if (config != null) {
+                nodeName = config.getNodeName();
+            }
+        }
+        return nodeName;
+    }
+
+    /**
+     * Is the operation permitted for the given level
+     * 
+     * @param level
+     */
+    private boolean authorize(UserLevel level) {
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+               return false;
+        }
+        
+        String username = SecurityContextHolder.getContext().getAuthentication().getName();
+        UserLevel userLevel = userManager.getUserLevel(username);
+        if (userLevel.toNumber() <= level.toNumber()) {
+               return true;
+        }
+        return false;
+    }
+    
+    private StatusJsonBean unauthorizedMessage() {
+       StatusJsonBean message = new StatusJsonBean();
+       message.setStatus(false);
+       message.setMessage("Operation not authorized");
+       return message;
+    }
+
+}
diff --git a/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/DevicesJsonBean.java b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/DevicesJsonBean.java
new file mode 100644 (file)
index 0000000..6b77f11
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.devices.web;
+
+import java.util.List;
+import java.util.Map;
+
+public class DevicesJsonBean {
+    private List<String> columnNames;
+    private List<Map<String, String>> nodeData;
+
+    public List<String> getColumnNames() {
+        return columnNames;
+    }
+
+    public void setColumnNames(List<String> columnNames) {
+        this.columnNames = columnNames;
+    }
+
+    public List<Map<String, String>> getNodeData() {
+        return nodeData;
+    }
+
+    public void setNodeData(List<Map<String, String>> nodeData) {
+        this.nodeData = nodeData;
+    }
+}
diff --git a/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/StatusJsonBean.java b/opendaylight/web/devices/src/main/java/org/opendaylight/controller/devices/web/StatusJsonBean.java
new file mode 100644 (file)
index 0000000..cbfc121
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.devices.web;
+
+public class StatusJsonBean {
+       private boolean status;
+       private String message;
+       public boolean isStatus() {
+               return status;
+       }
+       public void setStatus(boolean status) {
+               this.status = status;
+       }
+       public String getMessage() {
+               return message;
+       }
+       public void setMessage(String message) {
+               this.message = message;
+       }
+}
diff --git a/opendaylight/web/devices/src/main/resources/META-INF/spring.factories b/opendaylight/web/devices/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/web/devices/src/main/resources/META-INF/spring.handlers b/opendaylight/web/devices/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/web/devices/src/main/resources/META-INF/spring.schemas b/opendaylight/web/devices/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/web/devices/src/main/resources/META-INF/spring.tooling b/opendaylight/web/devices/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/web/devices/src/main/resources/WEB-INF/Devices-servlet.xml b/opendaylight/web/devices/src/main/resources/WEB-INF/Devices-servlet.xml
new file mode 100644 (file)
index 0000000..6b0a731
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans xmlns="http://www.springframework.org/schema/beans"\r
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xmlns:context="http://www.springframework.org/schema/context"\r
+  xmlns:mvc="http://www.springframework.org/schema/mvc"\r
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \r
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd\r
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">\r
+\r
+  <context:component-scan base-package="org.opendaylight.controller.devices.web"/>\r
+  \r
+  <mvc:resources mapping="/js/**" location="/js/" />\r
+  <mvc:resources mapping="/css/**" location="/css/" />\r
+  <mvc:resources mapping="/img/**" location="/img/" />\r
+  <mvc:annotation-driven/>\r
+  \r
+  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">\r
+       <property name="prefix" value="/WEB-INF/jsp/"/>\r
+       <property name="suffix" value=".jsp"/>\r
+  </bean>\r
+</beans>\r
diff --git a/opendaylight/web/devices/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/web/devices/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/web/devices/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/web/devices/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..641042c
--- /dev/null
@@ -0,0 +1,120 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+
+       <http pattern="/css/**" security="none" />
+       <http pattern="/js/**" security="none" />
+       <http pattern="/images/**" security="none" />
+       <http pattern="/favicon.ico" security="none" />
+       <http pattern="/one/css/**" security="none" />
+       <http pattern="/one/js/**" security="none" />
+       <http pattern="/one/images/**" security="none" />
+
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="loginUrlAuthenticationEntryPoint">
+               <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+               <intercept-url pattern="/logout*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+
+
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
+               <custom-filter position="LOGOUT_FILTER" ref="logoutFilter" />
+               <custom-filter position="LAST" ref="controllerFilter" />
+               <remember-me services-ref="rememberMeServices" key="SDN" />
+       </http>
+       
+       <beans:bean id="controllerFilter"
+               class="org.opendaylight.controller.web.ControllerCustomFilter" />
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="authenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="authenticationFilter"
+               class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+               <beans:property name="authenticationFailureHandler"
+                       ref="authenticationFailureHandler" />
+               <beans:property name="authenticationSuccessHandler">
+                       <beans:bean
+                               class="org.opendaylight.controller.web.ControllerAuthenticationSuccessHandler">
+                               <beans:property name="targetUrlParameter" value="x-page-url" />
+                               <beans:property name="defaultTargetUrl" value="/" />
+                       </beans:bean>
+               </beans:property>
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.web.ControllerWebSecurityContextRepository" />
+
+       <beans:bean id="authenticationFailureHandler"
+               class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
+               <beans:property name="useForward" value="false" />
+               <beans:property name="defaultFailureUrl" value="/login" />              
+       </beans:bean>
+
+       <beans:bean id="loginUrlAuthenticationEntryPoint"
+               class="org.opendaylight.controller.web.ControllerLoginUrlAuthEntryPoint">
+               <beans:property name="loginFormUrl" value="/login" />
+       </beans:bean>
+
+       <beans:bean id="authenticationProviderWrapper"
+               class="org.opendaylight.controller.web.AuthenticationProviderWrapper" />
+
+    <!-- logout related -->
+    
+    <beans:bean id="logoutHandler"
+        class="org.opendaylight.controller.web.ControllerLogoutHandler" />
+        
+    <beans:bean id="securityContextLogoutHandler"
+        class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />    
+        
+            
+    <beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
+        <!-- if logout succeed then this is the URL -->
+        <beans:constructor-arg value="/login" />
+        <beans:constructor-arg>
+            <beans:list>
+                <beans:ref bean="logoutHandler"/>
+                <beans:ref bean="rememberMeServices"/>
+                <beans:ref bean="securityContextLogoutHandler"/>
+            </beans:list>
+        </beans:constructor-arg>
+        <beans:property name="filterProcessesUrl" value="/logout" />
+    </beans:bean>       
+        
+
+
+
+       <!-- remember me related -->
+       <beans:bean id="rememberMeFilter"
+               class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+       </beans:bean>
+
+       <beans:bean id="rememberMeServices"
+               class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
+               <beans:property name="userDetailsService" ref="userDetailsServiceRef" />
+               <beans:property name="key" value="SDN" />
+               <beans:property name="alwaysRemember" value="true"></beans:property>
+               <beans:property name="tokenValiditySeconds" value="3600" />
+               <beans:property name="cookieName" value="SDN-Controller" />
+       </beans:bean>
+
+       <beans:bean id="userDetailsServiceRef" class="org.opendaylight.controller.web.ControllerUserDetailsService" />
+
+
+       <beans:bean id="rememberMeAuthenticationProvider"
+               class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
+               <beans:property name="key" value="SDN" />
+       </beans:bean>
+       
+</beans:beans>
diff --git a/opendaylight/web/devices/src/main/resources/WEB-INF/web.xml b/opendaylight/web/devices/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..de9d077
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+       version="2.4">
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <listener>
+               <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <servlet>
+               <servlet-name>Devices</servlet-name>
+               <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+               <load-on-startup>1</load-on-startup>
+       </servlet>
+
+       <servlet-mapping>
+               <servlet-name>Devices</servlet-name>
+               <url-pattern>/</url-pattern>
+       </servlet-mapping>
+
+       <filter>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <listener>
+               <listener-class>org.opendaylight.controller.web.ControllerUISessionManager</listener-class>
+       </listener>
+</web-app>
diff --git a/opendaylight/web/devices/src/main/resources/js/page.js b/opendaylight/web/devices/src/main/resources/js/page.js
new file mode 100644 (file)
index 0000000..57ca7b3
--- /dev/null
@@ -0,0 +1,1010 @@
+/* 
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved. 
+ * 
+ * This program and the accompanying materials are made available under the 
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution, 
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+//PAGE Devices
+one.f = {};
+
+// specify dashlets and layouts
+
+one.f.dashlet = {
+       nodesLearnt : {
+               id : 'nodesLearnt',
+           name : 'Nodes Learnt'
+       },
+    staticRouteConfig : {
+        id : 'staticRouteConfig',
+        name : 'Static route Configuration'
+    },
+    subnetGatewayConfig : {
+        id : 'subnetGatewayConfig',
+        name : 'Subnet Gateway Configuration'
+    },
+    spanPortConfig : {
+        id : 'spanPortConfig',
+        name : 'SPAN Port Configuration'
+    }
+};
+
+one.f.menu = {
+    left : {
+        top : [
+            one.f.dashlet.nodesLearnt
+        ],
+        bottom : [
+            one.f.dashlet.staticRouteConfig
+        ]
+    },
+    right : {
+        top : [],
+        bottom : [
+            one.f.dashlet.subnetGatewayConfig,
+            one.f.dashlet.spanPortConfig
+        ]
+    }
+};
+
+/**Devices Modules */
+one.f.switchmanager = {
+       rootUrl: "one/devices",
+       createTable: function(columnNames, body) {
+               var tableAttributes = ["table-striped", "table-bordered", "table-condensed"];
+               var $table = one.lib.dashlet.table.table(tableAttributes);
+               var tableHeaders = columnNames;
+               var $thead = one.lib.dashlet.table.header(tableHeaders);
+               var $tbody = one.lib.dashlet.table.body(body, tableHeaders);
+               $table.append($thead)
+               .append($tbody);
+               return $table;
+       },
+       validateName: function(name) {
+               return name.match(/^[a-zA-Z0-9][a-zA-Z0-9_\-\.]{1,31}$/g) == null ? false : true;
+       }
+};
+
+one.f.switchmanager.nodesLearnt = {
+       id: {
+               dashlet: {
+                       popout: "one_f_switchmanager_nodesLearnt_id_dashlet_popout"
+               },
+               modal: {
+                       modal: "one_f_switchmanager_nodesLearnt_id_modal_modal",
+                       save: "one_f_switchmanager_nodesLearnt_id_modal_save",
+                       form: {
+                               nodeId: "one_f_switchmanager_nodesLearnt_id_modal_form_nodeid",
+                               nodeName : "one_f_switchmanager_nodesLearnt_id_modal_form_nodename",
+                               tier: "one_f_switchmanager_nodesLearnt_id_modal_form_tier",
+                               operationMode: "one_f_switchmanager_nodesLearnt_id_modal_form_opmode"
+                       }
+               }
+       },
+       dashlet: function($dashlet) {
+               var url = one.f.switchmanager.rootUrl + "/nodesLearnt";
+               one.lib.dashlet.empty($dashlet);
+               $dashlet.append(one.lib.dashlet.header(one.f.dashlet.nodesLearnt.name));
+
+               one.f.switchmanager.nodesLearnt.ajax.main(url, function(content) {
+                       var body = one.f.switchmanager.nodesLearnt.data.abridged(content);
+                       var $table = one.f.switchmanager.createTable(["Node Name", "Node ID", "Ports"], body);
+                       $dashlet.append($table);
+               });
+       },
+
+       ajax : {
+               main : function(url, callback) {
+                       $.getJSON(url, function(data) {
+                               callback(data);
+                       });
+               }
+       },
+       modal : {
+               initialize: {
+                       updateNode: function(evt) {
+                               var nodeId = decodeURIComponent(evt.target.id);
+                               var h3 = "Update node information";
+                   var footer = one.f.switchmanager.nodesLearnt.modal.footer.updateNode();
+                   var $modal = one.lib.modal.spawn(one.f.switchmanager.nodesLearnt.id.modal.modal, h3, "", footer);
+                   
+                   // bind save button
+                   $('#' + one.f.switchmanager.nodesLearnt.id.modal.save, $modal).click(function() {
+                       one.f.switchmanager.nodesLearnt.modal.save($modal);
+                   });
+                   // inject body (nodePorts)
+                   one.f.switchmanager.nodesLearnt.ajax.main(one.f.switchmanager.rootUrl + "/tiers", function(tiers) {
+                       var $body = one.f.switchmanager.nodesLearnt.modal.body.updateNode(nodeId, evt.target.switchDetails, tiers);
+                       one.lib.modal.inject.body($modal, $body);
+                       $modal.modal();
+                   });
+                       },
+                       popout: function() {
+                               var h3 = "Nodes Learnt";
+                   var footer = one.f.switchmanager.nodesLearnt.modal.footer.popout();
+                   var $modal = one.lib.modal.spawn(one.f.switchmanager.nodesLearnt.id.modal.modal, h3, "", footer);
+                   var $body = one.f.switchmanager.nodesLearnt.modal.body.popout($modal);
+                   return $modal;
+                       }
+               },
+               body: {
+                       updateNode: function(nodeId, switchDetails, tiers) {
+                               var $form = $(document.createElement('form'));
+                               var $fieldset = $(document.createElement('fieldset'));
+                               // node ID. not editable.
+                               var $label = one.lib.form.label("Node ID");
+                               var $input = one.lib.form.input("node id");
+                               $input.attr('id', one.f.switchmanager.nodesLearnt.id.modal.form.nodeId);
+                               $input.attr("disabled", true);
+                               $input.attr("value", nodeId);
+                               $fieldset.append($label).append($input);
+                               // node name
+                               var $label = one.lib.form.label("Node Name");
+                               var $input = one.lib.form.input("Node Name");
+                               $input.attr('id', one.f.switchmanager.nodesLearnt.id.modal.form.nodeName);
+                               if(switchDetails["nodeName"] != null) {
+                                       $input.attr('value', switchDetails["nodeName"]);
+                               }
+                               $fieldset.append($label).append($input);
+                               // node tier
+                               var $label = one.lib.form.label("Tier");
+                               var $select = one.lib.form.select.create(tiers);
+                               $select.attr('id', one.f.switchmanager.nodesLearnt.id.modal.form.tier);
+                               $select.val(switchDetails["tier"]);
+                               $fieldset.append($label).append($select);
+                               // operation mode
+                               var $label = one.lib.form.label("Operation Mode");
+                               var $select = one.lib.form.select.create(
+                                               ["Allow reactive forwarding", "Proactive forwarding only"]);
+                               $select.attr('id', one.f.switchmanager.nodesLearnt.id.modal.form.operationMode);
+                               $select.val(switchDetails["mode"]);
+                               $fieldset.append($label).append($select);
+                               
+                               $form.append($fieldset);
+                               return $form;
+                       },
+                       popout: function($modal) {
+                               var url = one.f.switchmanager.rootUrl + "/nodesLearnt";
+                               one.f.switchmanager.nodesLearnt.ajax.main(url, function(content) {
+                                       var tableContent = one.f.switchmanager.nodesLearnt.data.popout(content);
+                                       var $table = one.f.switchmanager.createTable(content.columnNames, tableContent);
+                                       one.lib.modal.inject.body($modal, $table);
+                               });
+                       }
+               },
+               save: function($modal) {
+                       var result = {};
+            result['nodeName'] = $('#' + one.f.switchmanager.nodesLearnt.id.modal.form.nodeName, $modal).val();
+            if(!one.f.switchmanager.validateName(result['nodeName'])) {
+               alert("Node name can contain alphabets numbers and characters _ - . upto 32 characters in length");
+               return;
+            }
+            result['nodeId'] = $('#' + one.f.switchmanager.nodesLearnt.id.modal.form.nodeId, $modal).val();
+            result['tier'] = $('#' + one.f.switchmanager.nodesLearnt.id.modal.form.tier, $modal).val();
+            result['operationMode'] = $('#' + one.f.switchmanager.nodesLearnt.id.modal.form.operationMode, $modal).val();
+            one.f.switchmanager.nodesLearnt.modal.ajax(result, 
+                               function(response) {
+                       if(response.status == true) {
+                               $modal.modal('hide');
+                               one.topology.update(); // refresh visual topology with new name
+                               // TODO: Identify dashlet by inserting a nodesLearnt div 
+                                       // in the dashlet() instead
+                               one.f.switchmanager.nodesLearnt.dashlet($("#left-top .dashlet"));
+                       } else {
+                               alert(response.message);
+                       }
+                       
+                   });
+               },
+               ajax: function(requestData, callback) {
+                       $.getJSON(one.f.switchmanager.rootUrl + "/nodesLearnt/update", requestData, function(response) {
+                               callback(response);
+                       });
+               },
+               footer: {
+                       updateNode: function() {
+                               var footer = [];
+                               if(one.role < 2) {
+                                       var saveButton = one.lib.dashlet.button.single("Save", one.f.switchmanager.nodesLearnt.id.modal.save, "btn-success", "");
+                           var $saveButton = one.lib.dashlet.button.button(saveButton);
+                           footer.push($saveButton);
+                               }
+                   return footer;
+                       },
+                       popout: function() {
+                               // TODO: Maybe put a close button in the footer?
+                               return [];
+                       }
+               }
+       },
+       // data functions
+       data : {
+               abridged : function(data) {
+                       var result = [];
+                       $.each(data.nodeData, function(key, value) {
+                               var tr = {};
+                               var entry = [];
+                               var nodenameentry = value["nodeName"] ? value["nodeName"] : "Click to update";
+                               // TODO: Move anchor tag creation to one.lib.form.
+                               var aTag = document.createElement("a");
+                               aTag.setAttribute("id", encodeURIComponent(value["nodeId"]));
+                               aTag.switchDetails = value;
+                               aTag.addEventListener("click", one.f.switchmanager.nodesLearnt.modal.initialize.updateNode);
+                               aTag.addEventListener("mouseover", function(evt) {
+                                       evt.target.style.cursor = "pointer";
+                               }, false);
+                               aTag.innerHTML = nodenameentry;
+                               entry.push(aTag);
+                               entry.push(value["nodeId"]);
+                               entry.push(value["ports"]);
+                               tr.entry = entry;
+                               result.push(tr);
+                       });
+                       return result;
+               },
+               popout : function(data) {
+                       var result = [];
+                       $.each(data.nodeData, function(key, value) {
+                               var tr = {};
+                               // fill up all the td's
+                               var entry = [];
+                               var nodenameentry = value["nodeName"] ? value["nodeName"] : "No name provided";
+                               entry.push(nodenameentry);
+                               entry.push(value["nodeId"]);
+                               entry.push(value["tierName"]);
+                               entry.push(value["mac"]);
+                               entry.push(value["ports"]);
+                               tr.entry = entry;
+                               result.push(tr);
+                       });
+                       return result;
+               }
+       }
+};
+
+one.f.switchmanager.subnetGatewayConfig = {
+       id: {
+               dashlet: {
+                       addIPAddress: "one_f_switchmanager_subnetGatewayConfig_id_dashlet_addIP",
+                       addPorts: "one_f_switchmanager_subnetGatewayConfig_id_dashlet_addPorts",
+                       removeIPAddress: "one_f_switchmanager_subnetGatewayConfig_id_dashlet_removeIP"
+               }, 
+               modal: {
+                       modal: "one_f_switchmanager_subnetGatewayConfig_id_modal_modal",
+                       save: "one_f_switchmanager_subnetGatewayConfig_id_modal_save",
+                       form: {
+                               name : "one_f_switchmanager_subnetGatewayConfig_id_modal_form_gatewayname",
+                               gatewayIPAddress : "one_f_switchmanager_subnetGatewayConfig_id_modal_form_gatewayipaddress",
+                               nodeId: "one_f_switchmanager_subnetGatewayConfig_id_modal_form_nodeid",
+                               ports: "one_f_switchmanager_subnetGatewayConfig_id_modal_form_ports"
+                       }
+               }
+       },
+       // device ajax calls
+       dashlet: function($dashlet) {
+               one.lib.dashlet.empty($dashlet);
+               $dashlet.append(one.lib.dashlet.header(one.f.dashlet.subnetGatewayConfig.name));
+               // Add gateway IP Address button
+               if(one.role < 2) {
+                       var button = one.lib.dashlet.button.single("Add Gateway IP Address", 
+                                       one.f.switchmanager.subnetGatewayConfig.id.dashlet.addIPAddress, "btn-primary", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               $button.click(function() {
+                       var $modal = one.f.switchmanager.subnetGatewayConfig.modal.initialize.gateway();
+                   $modal.modal();
+               });
+                       $dashlet.append($button);
+               
+               
+                       // Delete gateway ip address button
+               var button = one.lib.dashlet.button.single("Delete Gateway IP Address(es)", 
+                               one.f.switchmanager.subnetGatewayConfig.id.dashlet.removeIPAddress, "btn-primary", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               $button.click(function() {
+                       var requestData = {};
+                       var gatewaysToDelete = [];
+                       var checkedCheckBoxes = $("input:checked", $(this).closest(".dashlet").find("table"));
+                       checkedCheckBoxes.each(function(index, value) {
+                               gatewaysToDelete.push(checkedCheckBoxes[index].id);
+                       }); 
+                       if(gatewaysToDelete.length > 0) {
+                               requestData["gatewaysToDelete"] = gatewaysToDelete.toString();
+                       var url = one.f.switchmanager.rootUrl + "/subnetGateway/delete";
+                               one.f.switchmanager.subnetGatewayConfig.ajax.main(url, requestData, function(response) {
+                                       if(response.status == true) {
+                                               // refresh dashlet by passing dashlet div as param
+                               one.f.switchmanager.subnetGatewayConfig.dashlet($("#right-bottom .dashlet"));
+                                       } else {
+                                               alert(response.message);
+                                       }
+                               });
+                       } 
+               });
+               $dashlet.append($button);
+       
+                       // Add Ports button
+                       var button = one.lib.dashlet.button.single("Add Ports", 
+                                       one.f.switchmanager.subnetGatewayConfig.id.dashlet.addPorts, "btn-primary", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               $button.click(function() {
+                       var $modal = one.f.switchmanager.subnetGatewayConfig.modal.initialize.ports();
+                   $modal.modal();
+               });
+                       $dashlet.append($button);
+               }
+               var url = one.f.switchmanager.rootUrl + "/subnets";
+               one.f.switchmanager.subnetGatewayConfig.ajax.main(url, {}, function(content) {
+                       var body = one.f.switchmanager.subnetGatewayConfig.data.devices(content);
+                       // first column contains checkbox. no need for header
+                       content.columnNames.splice(0,0," ");
+                       var $table = one.f.switchmanager.createTable(content.columnNames, body);
+                       $dashlet.append($table);
+               });
+       },
+       ajax : {
+               main : function(url, requestData, callback) {
+                       $.getJSON(url, requestData, function(data) {
+                               callback(data);
+                       });
+               }
+       },
+       registry: {},
+       modal : {
+               initialize: {
+                       gateway: function() {
+                               var h3 = "Add Gateway IP Address";
+                   var footer = one.f.switchmanager.subnetGatewayConfig.modal.footer();
+                   var $modal = one.lib.modal.spawn(one.f.switchmanager.subnetGatewayConfig.id.modal.modal, h3, "", footer);
+                   // bind save button
+                   $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.save, $modal).click(function() {
+                       one.f.switchmanager.subnetGatewayConfig.modal.save.gateway($modal);
+                   });
+                   var $body = one.f.switchmanager.subnetGatewayConfig.modal.body.gateway();
+                   one.lib.modal.inject.body($modal, $body);
+                   return $modal;
+                       },
+                       ports: function() {
+                               var h3 = "Add Ports";
+                   var footer = one.f.switchmanager.subnetGatewayConfig.modal.footer();
+                   var $modal = one.lib.modal.spawn(one.f.switchmanager.subnetGatewayConfig.id.modal.modal, h3, "", footer);
+                   // bind save button
+                   $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.save, $modal).click(function() {
+                       one.f.switchmanager.subnetGatewayConfig.modal.save.ports($modal);
+                   });
+                   
+                   // TODO: Change to subnetGateway instead.
+                   one.f.switchmanager.spanPortConfig.modal.ajax.nodes(function(nodes, nodeports) {
+                       var $body = one.f.switchmanager.subnetGatewayConfig.modal.body.ports(nodes, nodeports);
+                       one.lib.modal.inject.body($modal, $body);
+                   });
+                   return $modal;
+                       }
+               },
+               save: {
+                       gateway: function($modal) {
+                               var result = {};
+                   result['gatewayName'] = $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.form.name, $modal).val();
+                   if(!one.f.switchmanager.validateName(result['gatewayName'])) {
+                       alert("Gateway name can contain alphabets numbers and characters _ - . upto 32 characters in length");
+                       return;
+                   }
+                   result['gatewayIPAddress'] = $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.form.gatewayIPAddress, $modal).val();
+                   one.f.switchmanager.subnetGatewayConfig.modal.ajax.gateway(result, 
+                                       function(response) {
+                               if(response.status == true) {
+                                       $modal.modal('hide');
+                                       one.f.switchmanager.subnetGatewayConfig.dashlet($("#right-bottom .dashlet"));
+                               } else {
+                                       alert(response.message);
+                               }
+                           });
+                       },
+                       ports: function($modal) {
+                               var result = {};
+                               var gatewayRegistryIndex = $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.form.name, $modal).val();
+                   result['portsName'] = one.f.switchmanager.subnetGatewayConfig.registry.gateways[gatewayRegistryIndex];
+                   result['nodeId'] = $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.form.nodeId, $modal).val();
+                   result['ports'] = $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.form.ports, $modal).val();
+                   if(!result['portsName'] || result['portsName'] == "") {
+                       alert("No gateway chosen. Cannot add port");
+                       return;
+                   }
+                   if(!result['nodeId'] || result['nodeId'] == "") {
+                       alert("Please select a node.");
+                       return;
+                   }
+                   if(!result['ports'] || result['ports'] == "") {
+                       alert("Please choose a port.");
+                       return;
+                   }
+                   one.f.switchmanager.subnetGatewayConfig.modal.ajax.ports(result, 
+                                       function(response) {
+                               if(response.status == true) {
+                                       $modal.modal('hide');
+                                       one.f.switchmanager.subnetGatewayConfig.dashlet($("#right-bottom .dashlet"));
+                               } else {
+                                       alert(response.message);
+                               }
+                               });
+                       }
+               },
+               body: {
+                       gateway: function() {
+                               var $form = $(document.createElement('form'));
+                               var $fieldset = $(document.createElement('fieldset'));
+                               // gateway name
+                               var $label = one.lib.form.label("Name");
+                               var $input = one.lib.form.input("Name");
+                               $input.attr('id', one.f.switchmanager.subnetGatewayConfig.id.modal.form.name);
+                               $fieldset.append($label).append($input);
+                               // gateway IP Mask 
+                               var $label = one.lib.form.label("Gateway IP Address/Mask");
+                               var $input = one.lib.form.input("Gateway IP Address/Mask");
+                               $input.attr('id', one.f.switchmanager.subnetGatewayConfig.id.modal.form.gatewayIPAddress);
+                               $fieldset.append($label).append($input);
+                               
+                               $form.append($fieldset);
+                               return $form;
+                       },
+                       ports: function(nodes, nodeports) {
+                               var $form = $(document.createElement('form'));
+                               var $fieldset = $(document.createElement('fieldset'));
+                               // gateways drop down
+                               var $label = one.lib.form.label("Gateway Name");
+                               var $select = one.lib.form.select.create(one.f.switchmanager.subnetGatewayConfig.registry.gateways);
+                               $select.attr('id', one.f.switchmanager.subnetGatewayConfig.id.modal.form.name);
+                               $select.val($select.find("option:first").val());
+                               $fieldset.append($label).append($select);
+
+                               // node ID
+                               var $label = one.lib.form.label("Node ID");
+                               var $select = one.lib.form.select.create(nodes);
+                               $select.attr('id', one.f.switchmanager.subnetGatewayConfig.id.modal.form.nodeId);
+                               one.lib.form.select.prepend($select, { '' : 'Please Select a Node' });
+                               $select.val($select.find("option:first").val());
+                               $fieldset.append($label).append($select);
+
+                               // bind onchange
+                               $select.change(function() {
+                                   // retrieve port value
+                                   var node = $(this).find('option:selected').attr('value');
+                                   one.f.switchmanager.subnetGatewayConfig.registry['currentNode'] = node;
+                                   var $ports = $('#' + one.f.switchmanager.subnetGatewayConfig.id.modal.form.ports);
+                                   var ports = nodeports[node];
+                                   one.lib.form.select.inject($ports, ports);
+                                   one.lib.form.select.prepend($ports, { '' : 'Please Select a Port' });
+                                   $ports.val($ports.find("option:first").val());
+                               });
+
+                               // ports
+                               var $label = one.lib.form.label("Select Port");
+                               var $select = one.lib.form.select.create();
+                               $select.attr('id', one.f.switchmanager.subnetGatewayConfig.id.modal.form.ports);
+                               $fieldset.append($label).append($select);
+                               
+                               $form.append($fieldset);
+                               return $form;
+                       }
+               },
+               ajax: {
+                       gateway: function(requestData, callback) {
+                               $.getJSON(one.f.switchmanager.rootUrl + "/subnetGateway/add", requestData, function(data) {
+                                       callback(data);
+                       });
+                       },
+                       ports: function(requestData, callback) {
+                               $.getJSON(one.f.switchmanager.rootUrl + "/subnetGateway/ports/add", requestData, function(data) {
+                                       callback(data);
+                       });
+                       }
+               },
+               footer : function() {
+            var footer = [];
+            if(one.role < 2) {
+                var saveButton = one.lib.dashlet.button.single("Save", one.f.switchmanager.subnetGatewayConfig.id.modal.save, "btn-success", "");
+                 var $saveButton = one.lib.dashlet.button.button(saveButton);
+                 footer.push($saveButton);
+            }
+            return footer;
+        }
+       },
+       // data functions
+       data : {
+               devices : function(data) {
+                       var result = [];
+                       one.f.switchmanager.subnetGatewayConfig.registry.gateways = [];
+                       $.each(data.nodeData, function(key, value) {
+                               var tr = {};
+                               // fill up all the td's
+                               var subnetConfigObject = $.parseJSON(value["json"]);
+                               var nodePorts = subnetConfigObject.nodePorts;
+                               var $nodePortsContainer = $(document.createElement("div"));
+                               
+                               for(var i = 0; i < nodePorts.length; i++) {
+                                       var nodePort = nodePorts[i];
+                                       $nodePortsContainer.append(nodePort + " ");
+                                       // add delete anchor tag to delete ports
+                                       var aTag = document.createElement("a");
+                                       aTag.setAttribute("id", encodeURIComponent(nodePort));
+                                       aTag.gatewayName = value["name"];
+                                       aTag.addEventListener("click", function(evt) {
+                                               var htmlPortAnchor = evt.target;
+                                               var requestData = {};
+                                               requestData["gatewayName"] = evt.target.gatewayName;
+                                               requestData["nodePort"] = decodeURIComponent(evt.target.id);
+                                               // make ajax call to delete port
+                                               var url = one.f.switchmanager.rootUrl + "/subnetGateway/ports/delete";
+                                       one.f.switchmanager.subnetGatewayConfig.ajax.main(url, requestData, function(response) {
+                                               if(response.status == true) {
+                                                       // refresh dashlet by passing dashlet div as param
+                                       one.f.switchmanager.subnetGatewayConfig.dashlet($("#right-bottom .dashlet"));
+                                               } else {
+                                                       alert(response.message);
+                                               }
+                                       });
+                                               
+                                       });
+                                       aTag.addEventListener("mouseover", function(evt) {
+                                               evt.target.style.cursor = "pointer";
+                                       }, false);
+                                       aTag.innerHTML = "Delete";
+                                       $nodePortsContainer.append(aTag);
+                                       $nodePortsContainer.append("<br/>");
+                               }
+
+                               // store gateways in the registry so that they can be used in the add ports popup
+                               one.f.switchmanager.subnetGatewayConfig.registry.gateways.push(value["name"]);
+                               var entry = [];
+                               var checkbox = document.createElement("input");
+                               checkbox.setAttribute("type", "checkbox");
+                               checkbox.setAttribute("id", value["name"]);
+                               entry.push(checkbox);
+                               entry.push(value["name"]);
+                               entry.push(value["subnet"]);
+                               entry.push($nodePortsContainer);
+                               tr.entry = entry;
+                               result.push(tr);
+                       });
+                       return result;
+               }
+       }
+}
+
+one.f.switchmanager.staticRouteConfig = {
+       id: {
+               dashlet: {
+                       add: "one_f_switchmanager_staticRouteConfig_id_dashlet_add",
+                       remove: "one_f_switchmanager_staticRouteConfig_id_dashlet_remove"
+               }, 
+               modal: {
+                       modal: "one_f_switchmanager_staticRouteConfig_id_modal_modal",
+                       save: "one_f_switchmanager_staticRouteConfig_id_modal_save",
+                       form: {
+                               routeName : "one_f_switchmanager_staticRouteConfig_id_modal_form_routename",
+                               staticRoute : "one_f_switchmanager_staticRouteConfig_id_modal_form_staticroute",
+                nextHop : "one_f_switchmanager_staticRouteConfig_id_modal_form_nexthop",
+                       }
+               }
+       },
+       dashlet: function($dashlet) {
+               one.lib.dashlet.empty($dashlet);
+               
+               if(one.role < 2) {
+                       // Add static route button
+                       var button = one.lib.dashlet.button.single("Add Static Route", 
+                                       one.f.switchmanager.staticRouteConfig.id.dashlet.add, "btn-primary", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               $button.click(function() {
+                       var $modal = one.f.switchmanager.staticRouteConfig.modal.initialize();
+                   $modal.modal();
+               });
+               $dashlet.append(one.lib.dashlet.header(one.f.dashlet.staticRouteConfig.name));
+               $dashlet.append($button);
+               
+               // Delete static route button
+               var button = one.lib.dashlet.button.single("Delete Static Route(s)", 
+                               one.f.switchmanager.staticRouteConfig.id.dashlet.remove, "btn-primary", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               $button.click(function() {
+                       var requestData = {};
+                       var routesToDelete = [];
+                       var checkedCheckBoxes = $("input:checked", $(this).closest(".dashlet").find("table"));
+                       checkedCheckBoxes.each(function(index, value) {
+                               routesToDelete.push(checkedCheckBoxes[index].id);
+                       }); 
+                       if(routesToDelete.length > 0) {
+                               requestData["routesToDelete"] = routesToDelete.toString();
+                       var url = one.f.switchmanager.rootUrl + "/staticRoute/delete";
+                               one.f.switchmanager.staticRouteConfig.ajax.main(url, requestData, function(response) {
+                                       if(response.status == true) {
+                                               // refresh dashlet by passing dashlet div as param
+                               one.f.switchmanager.staticRouteConfig.dashlet($("#left-bottom .dashlet"));
+                                       } else {
+                                               alert(response.message);
+                                       }
+                               });
+                       } 
+               });
+               $dashlet.append($button);
+               }
+               
+               var url = one.f.switchmanager.rootUrl + "/staticRoutes";
+               one.f.switchmanager.staticRouteConfig.ajax.main(url, {}, function(content) {
+                       var body = one.f.switchmanager.staticRouteConfig.data.staticRouteConfig(content);
+                       // first column contains checkbox. no need for header
+                       content.columnNames.splice(0,0," ");
+                       var $table = one.f.switchmanager.createTable(content.columnNames, body);
+                       $dashlet.append($table);
+               });
+       },
+       // device ajax calls
+       ajax : {
+               main : function(url, requestData, callback) {
+                       $.getJSON(url, requestData, function(data) {
+                               callback(data);
+                       });
+               }
+       },
+       registry: {},
+       modal : {
+               initialize: function() {
+                       var h3 = "Add Static Route";
+            var footer = one.f.switchmanager.staticRouteConfig.modal.footer();
+            var $modal = one.lib.modal.spawn(one.f.switchmanager.staticRouteConfig.id.modal.modal, h3, "", footer);
+            // bind save button
+            $('#' + one.f.switchmanager.staticRouteConfig.id.modal.save, $modal).click(function() {
+               one.f.switchmanager.staticRouteConfig.modal.save($modal);
+            });
+            var $body = one.f.switchmanager.staticRouteConfig.modal.body();
+            one.lib.modal.inject.body($modal, $body);
+            return $modal;
+               },
+               save: function($modal) {
+                       var result = {};
+            result['routeName'] = $('#' + one.f.switchmanager.staticRouteConfig.id.modal.form.routeName, $modal).val();
+            result['staticRoute'] = $('#' + one.f.switchmanager.staticRouteConfig.id.modal.form.staticRoute, $modal).val();
+            result['nextHop'] = $('#' + one.f.switchmanager.staticRouteConfig.id.modal.form.nextHop, $modal).val();
+                       one.f.switchmanager.staticRouteConfig.modal.ajax.staticRouteConfig(result, function(response) {
+                       if(response.status == true) {
+                               $modal.modal('hide');
+                               // refresh dashlet by passing dashlet div as param
+                               one.f.switchmanager.staticRouteConfig.dashlet($("#left-bottom .dashlet"));
+                       } else {
+                               // TODO: Show error message in a error message label instead.
+                               alert(response.message);
+                       }
+                   });
+               },
+               body: function() {
+                       var $form = $(document.createElement('form'));
+                       var $fieldset = $(document.createElement('fieldset'));
+                       // static route name
+                       var $label = one.lib.form.label("Name");
+                       var $input = one.lib.form.input("Name");
+                       $input.attr('id', one.f.switchmanager.staticRouteConfig.id.modal.form.routeName);
+                       $fieldset.append($label).append($input);
+                       // static route IP Mask 
+                       var $label = one.lib.form.label("Static Route");
+                       var $input = one.lib.form.input("Static Route");
+                       $input.attr('id', one.f.switchmanager.staticRouteConfig.id.modal.form.staticRoute);
+                       $fieldset.append($label).append($input);
+                       // static route IP Mask 
+                       var $label = one.lib.form.label("Next Hop");
+                       var $input = one.lib.form.input("Next Hop");
+                       $input.attr('id', one.f.switchmanager.staticRouteConfig.id.modal.form.nextHop);
+                       $fieldset.append($label).append($input);
+                       // return
+                       $form.append($fieldset);
+                       return $form;
+               },
+               ajax: {
+                       staticRouteConfig: function(requestData, callback) {
+                               $.getJSON(one.f.switchmanager.rootUrl + "/staticRoute/add", requestData, function(data) {
+                                       callback(data);
+                               });
+                       }
+               },
+               data : {
+            
+        },
+               footer : function() {
+            var footer = [];
+            if (one.role < 2) {
+                var saveButton = one.lib.dashlet.button.single("Save", one.f.switchmanager.staticRouteConfig.id.modal.save, "btn-success", "");
+                 var $saveButton = one.lib.dashlet.button.button(saveButton);
+                 footer.push($saveButton);
+            }
+            return footer;
+        }
+       },
+       // data functions
+       data : {
+               staticRouteConfig : function(data) {
+                       var result = [];
+                       $.each(data.nodeData, function(key, value) {
+                               var tr = {};
+                               // fill up all the td's
+                               var entry = [];
+                               var checkbox = document.createElement("input");
+                               checkbox.setAttribute("type", "checkbox");
+                               checkbox.setAttribute("id", value["name"]);
+                               entry.push(checkbox);
+                               entry.push(value["name"]);
+                               entry.push(value["staticRoute"]);
+                               entry.push(value["nextHop"]);
+                               tr.entry = entry;
+                               result.push(tr);
+                       });
+                       return result;
+               }
+       }
+}
+
+one.f.switchmanager.spanPortConfig = {
+       id: {
+               dashlet: {
+                       add: "one_f_switchmanager_spanPortConfig_id_dashlet_add",
+                       remove: "one_f_switchmanager_spanPortConfig_id_dashlet_remove"
+               }, 
+               modal: {
+                       modal: "one_f_switchmanager_spanPortConfig_id_modal_modal",
+                       save: "one_f_switchmanager_spanPortConfig_id_modal_save",
+                       form: {
+                               name : "one_f_switchmanager_spanPortConfig_id_modal_form_name",
+                               nodes : "one_f_switchmanager_spanPortConfig_id_modal_form_nodes",
+                port : "one_f_switchmanager_spanPortConfig_id_modal_form_port",
+                       }
+               }
+       },
+       dashlet: function($dashlet) {
+               one.lib.dashlet.empty($dashlet);
+               if(one.role < 2) {
+
+                       // Add span port button
+                       var button = one.lib.dashlet.button.single("Add Span Port", one.f.switchmanager.spanPortConfig.id.dashlet.add, "btn-primary", "btn-mini");
+                       var $button = one.lib.dashlet.button.button(button);
+
+                       $button.click(function() {
+                               var $modal = one.f.switchmanager.spanPortConfig.modal.initialize();
+                               $modal.modal();
+                       });
+                       $dashlet.append(one.lib.dashlet.header(one.f.dashlet.spanPortConfig.name));
+                       $dashlet.append($button);
+
+                       // Delete span port button
+                       var button = one.lib.dashlet.button.single("Delete SPAN Port(s)", 
+                                       one.f.switchmanager.spanPortConfig.id.dashlet.remove, "btn-primary", "btn-mini");
+                       var $button = one.lib.dashlet.button.button(button);
+                       $button.click(function() {
+
+                               var checkedCheckBoxes = $("input:checked", $(this).closest(".dashlet").find("table"));
+                               if(checkedCheckBoxes.length > 0) {
+                                       var spanPortsToDelete = "";
+                                       checkedCheckBoxes.each(function(index, value) {
+                                               spanPortsToDelete += checkedCheckBoxes[index].spanPort + "###";
+                                       }); 
+
+                                       var requestData = {};
+                                       requestData["spanPortsToDelete"] = spanPortsToDelete;
+                                       var url = one.f.switchmanager.rootUrl + "/spanPorts/delete";
+                                       one.f.switchmanager.spanPortConfig.ajax.main(url, requestData, function(response) {
+                                               if(response.status == true) {
+                                                       // refresh dashlet by passing dashlet div as param
+                                                       one.f.switchmanager.spanPortConfig.dashlet($("#right-bottom .dashlet"));
+                                               } else {
+                                                       alert(response.message);
+                                               }
+                                       });
+                               }
+                       });
+                       $dashlet.append($button);
+               }
+        
+        //populate table in dashlet
+               var url = one.f.switchmanager.rootUrl + "/spanPorts";
+               one.f.switchmanager.spanPortConfig.ajax.main(url, {}, function(content) {
+                       var body = one.f.switchmanager.spanPortConfig.data.devices(content);
+                       // first column contains the checkbox. no header required.
+                       content.columnNames.splice(0,0," ");
+                       var $table = one.f.switchmanager.createTable(content.columnNames, body);
+                       $dashlet.append($table);
+               });
+       },
+       // device ajax calls
+       ajax : {
+               main : function(url, requestData, callback) {
+                       $.getJSON(url, requestData, function(data) {
+                               callback(data);
+                       });
+               }
+       },
+       registry: {},
+       modal : {
+               initialize: function() {
+                       var h3 = "Add SPAN Port";
+            var footer = one.f.switchmanager.spanPortConfig.modal.footer();
+            var $modal = one.lib.modal.spawn(one.f.switchmanager.spanPortConfig.id.modal.modal, h3, "", footer);
+            // bind save button
+            $('#' + one.f.switchmanager.spanPortConfig.id.modal.save, $modal).click(function() {
+               one.f.switchmanager.spanPortConfig.modal.save($modal);
+            });
+
+            one.f.switchmanager.spanPortConfig.modal.ajax.nodes(function(nodes, nodeports) {
+                var $body = one.f.switchmanager.spanPortConfig.modal.body(nodes, nodeports);
+                one.lib.modal.inject.body($modal, $body);
+            });
+            return $modal;
+               },
+               save: function($modal) {
+                       var result = {};
+            result['nodeId'] = $('#' + one.f.switchmanager.spanPortConfig.id.modal.form.nodes, $modal).val();
+            result['spanPort'] = $('#' + one.f.switchmanager.spanPortConfig.id.modal.form.port, $modal).val();
+                       one.f.switchmanager.spanPortConfig.modal.ajax.saveSpanPortConfig(result, 
+                               function(response) {
+                                       if(response.status == true) {
+                                               $modal.modal('hide');
+                                               one.f.switchmanager.spanPortConfig.dashlet($("#right-bottom .dashlet"));
+                                       } else {
+                                               alert(response.message);
+                                       }
+                       
+                   });
+               },
+               body: function(nodes, nodeports) {
+                       var $form = $(document.createElement('form'));
+                       var $fieldset = $(document.createElement('fieldset'));
+                       // node
+                       var $label = one.lib.form.label("Node");
+                       var $select = one.lib.form.select.create(nodes);
+                       one.lib.form.select.prepend($select, { '' : 'Please Select a Node' });
+                       $select.attr('id', one.f.switchmanager.spanPortConfig.id.modal.form.nodes);
+                       
+                       // bind onchange
+                       $select.change(function() {
+                           // retrieve port value
+                           var node = $(this).find('option:selected').attr('value');
+                           one.f.switchmanager.spanPortConfig.registry['currentNode'] = node;
+                           var $ports = $('#' + one.f.switchmanager.spanPortConfig.id.modal.form.port);
+                           var ports = nodeports[node];
+                           one.lib.form.select.inject($ports, ports);
+                       });
+
+            $fieldset.append($label).append($select);
+                       // input port
+                       var $label = one.lib.form.label("Input Port");
+                       var $select = one.lib.form.select.create();
+                       $select.attr('id', one.f.switchmanager.spanPortConfig.id.modal.form.port);
+                       $fieldset.append($label).append($select);
+                       
+                       // return
+                       $form.append($fieldset);
+                       return $form;
+               },
+               ajax: {
+                       nodes: function(callback) {
+                               $.getJSON(one.f.switchmanager.rootUrl + "/nodeports", function(data) {
+                    var nodes = one.f.switchmanager.spanPortConfig.modal.data.nodes(data);
+                    var nodeports = data;
+                    one.f.switchmanager.spanPortConfig.registry['nodeports'] = nodeports;
+                    callback(nodes, nodeports);
+                });
+                       },
+                       saveSpanPortConfig: function(requestData, callback) {
+                               var resource = {};
+                               resource["jsonData"] = JSON.stringify(requestData);
+                               $.getJSON(one.f.switchmanager.rootUrl + "/spanPorts/add", resource, function(data) {
+                                       callback(data);
+                               });
+                       }
+               },
+               data : {
+            nodes : function(data) {
+                result = {};
+                $.each(data, function(key, value) {
+                    result[key] = key;
+                });
+                return result;
+            }
+        },
+               footer : function() {
+            var footer = [];
+            if (one.role < 2) {
+                var saveButton = one.lib.dashlet.button.single("Save", one.f.switchmanager.spanPortConfig.id.modal.save, "btn-success", "");
+                var $saveButton = one.lib.dashlet.button.button(saveButton);
+                footer.push($saveButton);
+            }
+            return footer;
+        }
+       },
+       // data functions
+       data : {
+               devices : function(data) {
+                       var result = [];
+                       $.each(data.nodeData, function(key, value) {
+                               var tr = {};
+                               // fill up all the td's
+                               var entry = [];
+                               var checkbox = document.createElement("input");
+                               checkbox.setAttribute("type", "checkbox");
+                               checkbox.spanPort = value.json;
+                               entry.push(checkbox);
+                               entry.push(value["nodeName"]);
+                               entry.push(value["spanPort"]);
+                               tr.entry = entry;
+                               result.push(tr);
+                       });
+                       return result;
+               }
+       }
+}
+
+/** INIT **/
+// populate nav tabs
+$(one.f.menu.left.top).each(function(index, value) {
+    var $nav = $(".nav", "#left-top");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.left.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#left-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.right.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#right-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+one.f.addPopOut = function() {
+       $img1 = $(document.createElement("img"));
+       $img1.attr("src", "/img/Expand16T.png");
+       $img1.attr("style", "float: right;");
+       $img1.hover(function() {
+               $img1.css("cursor", "pointer");
+       });
+       $img1.click(function() {
+               var $modal = one.f.switchmanager.nodesLearnt.modal.initialize.popout();
+       $modal.css('width', 'auto');
+               $modal.css('margin-left', '-40%');
+        $modal.modal();
+       });
+       $dash1 = $($("#left-top .nav")[0]);
+       $dash1.append($img1);
+};
+one.f.addPopOut();
+
+// bind dashlet nav
+$('.dash .nav a', '#main').click(function() {
+    // de/activation
+    var $li = $(this).parent();
+    var $ul = $li.parent();
+    one.lib.nav.unfocus($ul);
+    $li.addClass('active');
+    // clear respective dashlet
+    var $dashlet = $ul.parent().find('.dashlet');
+    one.lib.dashlet.empty($dashlet);
+
+    // callback based on menu
+    var id = $(this).attr('id');
+    var menu = one.f.dashlet;
+    switch (id) {
+        case menu.nodesLearnt.id:
+               one.f.switchmanager.nodesLearnt.dashlet($dashlet);
+            break;
+        case menu.staticRouteConfig.id:
+               one.f.switchmanager.staticRouteConfig.dashlet($dashlet);
+            break;
+        case menu.subnetGatewayConfig.id:
+               one.f.switchmanager.subnetGatewayConfig.dashlet($dashlet);
+            break;
+        case menu.spanPortConfig.id:
+               one.f.switchmanager.spanPortConfig.dashlet($dashlet);
+            break;
+    };
+});
+
+// activate first tab on each dashlet
+$('.dash .nav').each(function(index, value) {
+    $($(value).find('li')[0]).find('a').click();
+});
\ No newline at end of file
diff --git a/opendaylight/web/flows/pom.xml b/opendaylight/web/flows/pom.xml
new file mode 100644 (file)
index 0000000..62c8922
--- /dev/null
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../../commons/opendaylight</relativePath>
+       </parent>
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>flows.web</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Export-Package>
+                                               </Export-Package>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.forwardingrulesmanager,
+                                                       org.opendaylight.controller.sal.authorization,
+                                                       org.opendaylight.controller.sal.action,
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.flowprogrammer,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.containermanager,
+                                                       org.opendaylight.controller.switchmanager,
+                                                       org.opendaylight.controller.usermanager,
+                                                       org.opendaylight.controller.web,
+                                                       com.google.gson,
+                                                       javax.annotation,
+                                                       javax.naming,
+                                                       javax.servlet,
+                                                       javax.servlet.annotation,
+                                                       javax.servlet.http,
+                                                       javax.servlet.jsp,
+                                                       javax.servlet.jsp.el,
+                                                       javax.servlet.jsp.jstl.core,
+                                                       javax.servlet.jsp.jstl.fmt,
+                                                       javax.servlet.jsp.jstl.tlv,
+                                                       javax.servlet.jsp.tagext,
+                                                       javax.servlet.resources,
+                                                       javax.xml.parsers,
+                                                       javax.xml.transform,
+                                                       org.apache.commons.logging,
+                                                       org.apache.taglibs.standard.functions,
+                                                       org.apache.taglibs.standard.resources,
+                                                       org.apache.taglibs.standard.tag.common.core,
+                                                       org.apache.taglibs.standard.tag.common.fmt,
+                                                       org.apache.taglibs.standard.tag.rt.core,
+                                                       org.apache.taglibs.standard.tag.rt.fmt,
+                                                       org.apache.taglibs.standard.tei,
+                                                       org.apache.taglibs.standard.tlv,
+                                                       org.codehaus.jackson,
+                                                       org.codehaus.jackson.annotate,
+                                                       org.codehaus.jackson.map,
+                                                       org.codehaus.jackson.map.annotate,
+                                                       org.osgi.framework,
+                                                       org.slf4j,
+                                                       org.springframework.beans,
+                                                       org.springframework.beans.factory.xml,
+                                                       org.springframework.context.config,
+                                                       org.springframework.stereotype,
+                                                       org.springframework.web,
+                                                       org.springframework.web.bind.annotation,
+                                                       org.springframework.web.servlet,
+                                                       org.springframework.web.servlet.config,
+                                                       org.springframework.web.servlet.view,
+
+                                                       org.springframework.web.filter,
+                                                       org.springframework.web.context,
+                                                       org.springframework.security.core,
+                                                       org.springframework.security.core.userdetails,
+                                                       org.springframework.security.core.authority,
+                                                       org.springframework.security.core.context,
+                                                       org.springframework.security.authentication,
+                                                       org.springframework.security.config,
+                                                       org.springframework.security.config.authentication,
+                                                       org.springframework.security.taglibs.authz,
+                                                       org.springframework.security.web,
+                                                       org.springframework.security.web.context,
+                                                       org.springframework.security.web.authentication,
+                                                       org.springframework.security.web.authentication.www,
+                                                       org.springframework.security.provisioning,
+                                                       org.springframework.security.web.util,
+                                                       org.springframework.security.web.authentication.rememberme,
+                                                       org.springframework.security.web.authentication.logout,
+                                                       org.springframework.dao\r
+                                               </Import-Package>
+                                               <Web-ContextPath>/one/flows</Web-ContextPath>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>forwardingrulesmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>containermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>usermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>switchmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>web</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java b/opendaylight/web/flows/src/main/java/org/opendaylight/controller/flows/web/Flows.java
new file mode 100644 (file)
index 0000000..297c99c
--- /dev/null
@@ -0,0 +1,253 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.flows.web;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.web.IOneWeb;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.google.gson.Gson;
+
+@Controller
+@RequestMapping("/")
+public class Flows implements IOneWeb {
+       private static final UserLevel AUTH_LEVEL = UserLevel.CONTAINERUSER;
+    private final String WEB_NAME = "Flows";
+    private final String WEB_ID = "flows";
+    private final short WEB_ORDER = 2;
+
+    public Flows() {
+        ServiceHelper.registerGlobalService(IOneWeb.class, this, null);
+    }
+
+    @Override
+    public String getWebName() {
+        return WEB_NAME;
+    }
+
+    @Override
+    public String getWebId() {
+        return WEB_ID;
+    }
+
+    @Override
+    public short getWebOrder() {
+        return WEB_ORDER;
+    }
+
+       @Override
+       public boolean isAuthorized(UserLevel userLevel) {
+               return userLevel.ordinal() <= AUTH_LEVEL.ordinal();
+       }
+       
+    @RequestMapping(value = "/main")
+    @ResponseBody
+    public Set<Map<String, Object>> getFlows() {
+        // fetch frm
+        IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper
+                .getInstance(IForwardingRulesManager.class, "default", this);
+        if (frm == null)
+            return null;
+
+        // fetch sm
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, "default", this);
+        if (switchManager == null)
+            return null;
+        
+        // get static flow list
+        List<FlowConfig> staticFlowList = frm.getStaticFlows();
+        Set<Map<String, Object>> output = new HashSet<Map<String, Object>>();
+        for (FlowConfig flowConfig : staticFlowList) {
+               Map<String, Object> entry = new HashMap<String, Object>();
+               entry.put("flow", flowConfig);
+               entry.put("name", flowConfig.getName());
+               
+               Node node = flowConfig.getNode(); 
+               SwitchConfig switchConfig = switchManager.getSwitchConfig(node.getNodeIDString());
+               String nodeName = node.toString();
+               if (switchConfig != null) { nodeName = switchConfig.getNodeName(); }
+               entry.put("node", nodeName);
+               entry.put("nodeId", node.toString());
+               output.add(entry);
+        }
+        
+        return output;
+    }
+
+    @RequestMapping(value = "/node-ports")
+    @ResponseBody
+    public Map<String, Object> getNodePorts() {
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, "default", this);
+        if (switchManager == null)
+            return null;
+
+        Map<String, Object> nodes = new HashMap<String, Object>();
+        Map<Short, String> port;
+
+        for (Switch node : switchManager.getNetworkDevices()) {
+            port = new HashMap<Short, String>(); // new port
+            Set<NodeConnector> nodeConnectorSet = node.getNodeConnectors();
+
+            if (nodeConnectorSet != null)
+                for (NodeConnector nodeConnector : nodeConnectorSet) {
+                    String nodeConnectorName = ((Name) switchManager
+                            .getNodeConnectorProp(nodeConnector,
+                                    Name.NamePropName)).getValue();
+                    port.put((Short) nodeConnector.getID(),
+                             nodeConnectorName + "("
+                             + nodeConnector.getNodeConnectorIDString() + ")");
+                }
+            
+            // add ports
+            Map<String, Object> entry = new HashMap<String, Object>();
+            entry.put("ports", port);
+            
+            // add name
+            String nodeName = node.getNode().toString();
+            SwitchConfig config = switchManager.getSwitchConfig(node.getNode().getNodeIDString());
+            if (config != null) {
+               nodeName = config.getNodeName();
+            }
+            entry.put("name", nodeName);
+            
+            // add to the node
+            nodes.put(node.getNode().toString(), entry);
+        }
+
+        return nodes;
+    }
+    
+    @RequestMapping(value = "/node-flows")
+    @ResponseBody
+    public Map<String, Object> getNodeFlows() {
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, "default", this);
+        if (switchManager == null) { return null; }
+        IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper
+                .getInstance(IForwardingRulesManager.class, "default", this);
+        if (frm == null) { return null; }
+
+        Map<String, Object> nodes = new HashMap<String, Object>();
+
+        for (Switch sw : switchManager.getNetworkDevices()) {
+            Node node = sw.getNode();
+            
+            List<FlowConfig> flows = frm.getStaticFlows(node);
+            
+            String nodeName = node.toString();
+            SwitchConfig config = switchManager.getSwitchConfig(node.getNodeIDString());
+            if (config != null) {
+               nodeName = config.getNodeName();
+            }
+            
+            nodes.put(nodeName, flows.size());
+        }
+
+        return nodes;
+    }
+
+    @RequestMapping(value = "/flow", method = RequestMethod.POST)
+    @ResponseBody
+    public String actionFlow(@RequestParam(required = true) String action,
+            @RequestParam(required = false) String body, @RequestParam(required = true) String nodeId) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return "Operation not authorized";
+       }
+       
+        IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper
+                .getInstance(IForwardingRulesManager.class, "default", this);
+        if (frm == null) { return null; }
+
+        Gson gson = new Gson();
+        FlowConfig flow = gson.fromJson(body, FlowConfig.class);
+        Node node = Node.fromString(nodeId);
+        flow.setNode(node);
+        Status result = null;
+        if (action.equals("add")) {
+            result = frm.addStaticFlow(flow, false);
+        }
+
+        return result.getDescription();
+    }
+    
+    @RequestMapping(value = "/flow/{nodeId}/{name}", method = RequestMethod.POST)
+    @ResponseBody
+    public String removeFlow(@PathVariable("nodeId") String nodeId, @PathVariable("name") String name,
+               @RequestParam(required = true) String action) {
+       if (!authorize(UserLevel.NETWORKADMIN)) { return "Operation not authorized"; }
+       
+       IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper
+                .getInstance(IForwardingRulesManager.class, "default", this);
+        if (frm == null) { return null; }
+        
+        Status result = null;
+        Node node = Node.fromString(nodeId);
+        if (node == null) {
+            return null;
+        }
+        if (action.equals("remove")) {
+               result = frm.removeStaticFlow(name, node);
+        } else if (action.equals("toggle")) {
+               FlowConfig config = frm.getStaticFlow(name, node);
+               result = frm.toggleStaticFlowStatus(config);
+        } else {
+               result = new Status(StatusCode.BADREQUEST, "Unknown action");
+        }
+        
+        return result.getDescription();
+    }
+    
+    /**
+     * Is the operation permitted for the given level
+     * 
+     * @param level
+     */
+    private boolean authorize(UserLevel level) {
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+               return false;
+        }
+        
+        String username = SecurityContextHolder.getContext().getAuthentication().getName();
+        UserLevel userLevel = userManager.getUserLevel(username);
+        if (userLevel.toNumber() <= level.toNumber()) {
+               return true;
+        }
+        return false;
+    }
+
+}
diff --git a/opendaylight/web/flows/src/main/resources/META-INF/spring.factories b/opendaylight/web/flows/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/web/flows/src/main/resources/META-INF/spring.handlers b/opendaylight/web/flows/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/web/flows/src/main/resources/META-INF/spring.schemas b/opendaylight/web/flows/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/web/flows/src/main/resources/META-INF/spring.tooling b/opendaylight/web/flows/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/web/flows/src/main/resources/WEB-INF/Flows-servlet.xml b/opendaylight/web/flows/src/main/resources/WEB-INF/Flows-servlet.xml
new file mode 100644 (file)
index 0000000..fbe6979
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans xmlns="http://www.springframework.org/schema/beans"\r
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xmlns:context="http://www.springframework.org/schema/context"\r
+  xmlns:mvc="http://www.springframework.org/schema/mvc"\r
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \r
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd\r
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">\r
+\r
+  <context:component-scan base-package="org.opendaylight.controller.flows.web"/>\r
+  \r
+  <mvc:resources mapping="/js/**" location="/js/" />\r
+  <mvc:resources mapping="/css/**" location="/css/" />\r
+  <mvc:resources mapping="/img/**" location="/img/" />\r
+  <mvc:annotation-driven/>\r
+  \r
+  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">\r
+       <property name="prefix" value="/WEB-INF/jsp/"/>\r
+       <property name="suffix" value=".jsp"/>\r
+  </bean>\r
+</beans>\r
diff --git a/opendaylight/web/flows/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/web/flows/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/web/flows/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/web/flows/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..641042c
--- /dev/null
@@ -0,0 +1,120 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+
+       <http pattern="/css/**" security="none" />
+       <http pattern="/js/**" security="none" />
+       <http pattern="/images/**" security="none" />
+       <http pattern="/favicon.ico" security="none" />
+       <http pattern="/one/css/**" security="none" />
+       <http pattern="/one/js/**" security="none" />
+       <http pattern="/one/images/**" security="none" />
+
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="loginUrlAuthenticationEntryPoint">
+               <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+               <intercept-url pattern="/logout*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+
+
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
+               <custom-filter position="LOGOUT_FILTER" ref="logoutFilter" />
+               <custom-filter position="LAST" ref="controllerFilter" />
+               <remember-me services-ref="rememberMeServices" key="SDN" />
+       </http>
+       
+       <beans:bean id="controllerFilter"
+               class="org.opendaylight.controller.web.ControllerCustomFilter" />
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="authenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="authenticationFilter"
+               class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+               <beans:property name="authenticationFailureHandler"
+                       ref="authenticationFailureHandler" />
+               <beans:property name="authenticationSuccessHandler">
+                       <beans:bean
+                               class="org.opendaylight.controller.web.ControllerAuthenticationSuccessHandler">
+                               <beans:property name="targetUrlParameter" value="x-page-url" />
+                               <beans:property name="defaultTargetUrl" value="/" />
+                       </beans:bean>
+               </beans:property>
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.web.ControllerWebSecurityContextRepository" />
+
+       <beans:bean id="authenticationFailureHandler"
+               class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
+               <beans:property name="useForward" value="false" />
+               <beans:property name="defaultFailureUrl" value="/login" />              
+       </beans:bean>
+
+       <beans:bean id="loginUrlAuthenticationEntryPoint"
+               class="org.opendaylight.controller.web.ControllerLoginUrlAuthEntryPoint">
+               <beans:property name="loginFormUrl" value="/login" />
+       </beans:bean>
+
+       <beans:bean id="authenticationProviderWrapper"
+               class="org.opendaylight.controller.web.AuthenticationProviderWrapper" />
+
+    <!-- logout related -->
+    
+    <beans:bean id="logoutHandler"
+        class="org.opendaylight.controller.web.ControllerLogoutHandler" />
+        
+    <beans:bean id="securityContextLogoutHandler"
+        class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />    
+        
+            
+    <beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
+        <!-- if logout succeed then this is the URL -->
+        <beans:constructor-arg value="/login" />
+        <beans:constructor-arg>
+            <beans:list>
+                <beans:ref bean="logoutHandler"/>
+                <beans:ref bean="rememberMeServices"/>
+                <beans:ref bean="securityContextLogoutHandler"/>
+            </beans:list>
+        </beans:constructor-arg>
+        <beans:property name="filterProcessesUrl" value="/logout" />
+    </beans:bean>       
+        
+
+
+
+       <!-- remember me related -->
+       <beans:bean id="rememberMeFilter"
+               class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+       </beans:bean>
+
+       <beans:bean id="rememberMeServices"
+               class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
+               <beans:property name="userDetailsService" ref="userDetailsServiceRef" />
+               <beans:property name="key" value="SDN" />
+               <beans:property name="alwaysRemember" value="true"></beans:property>
+               <beans:property name="tokenValiditySeconds" value="3600" />
+               <beans:property name="cookieName" value="SDN-Controller" />
+       </beans:bean>
+
+       <beans:bean id="userDetailsServiceRef" class="org.opendaylight.controller.web.ControllerUserDetailsService" />
+
+
+       <beans:bean id="rememberMeAuthenticationProvider"
+               class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
+               <beans:property name="key" value="SDN" />
+       </beans:bean>
+       
+</beans:beans>
diff --git a/opendaylight/web/flows/src/main/resources/WEB-INF/web.xml b/opendaylight/web/flows/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..5841ed2
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+       version="2.4">
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <listener>
+               <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <servlet>
+               <servlet-name>Flows</servlet-name>
+               <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+               <load-on-startup>1</load-on-startup>
+       </servlet>
+
+       <servlet-mapping>
+               <servlet-name>Flows</servlet-name>
+               <url-pattern>/</url-pattern>
+       </servlet-mapping>
+
+       <filter>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <listener>
+               <listener-class>org.opendaylight.controller.web.ControllerUISessionManager</listener-class>
+       </listener>
+
+</web-app>
diff --git a/opendaylight/web/flows/src/main/resources/js/page.js b/opendaylight/web/flows/src/main/resources/js/page.js
new file mode 100644 (file)
index 0000000..13884fb
--- /dev/null
@@ -0,0 +1,1259 @@
+
+/* 
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved. 
+ * 
+ * This program and the accompanying materials are made available under the 
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution, 
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+//PAGE flows
+one.f = {};
+
+// specify dashlets and layouts
+one.f.dashlet = {
+    flows : {
+        id : 'flows',
+        name : 'Flow Entries'
+    },
+    nodes : {
+        id : 'nodes',
+        name : 'Nodes'
+    },
+    detail : {
+        id : 'detail',
+        name : 'Flow Detail'
+    }
+};
+
+one.f.menu = {
+    left : {
+        top : [
+            one.f.dashlet.flows
+        ],
+        bottom : [
+            one.f.dashlet.nodes
+        ]
+    },
+    right : {
+        top : [],
+        bottom : [
+            one.f.dashlet.detail
+        ]
+    }
+};
+
+one.f.address = {
+    root : "/one/flows",
+    flows : {
+        main : "/main",
+               flows : "/node-flows",
+        nodes : "/node-ports",
+        flow : "/flow"
+    }
+}
+
+/** NODES **/
+one.f.nodes = {
+    id : {},
+    registry : {},
+    dashlet : function($dashlet) {
+        var $h4 = one.lib.dashlet.header("Nodes");
+        $dashlet.append($h4);
+        
+        // load body
+        one.f.nodes.ajax.dashlet(function($table) {
+                       // total nodes count
+                       var nodeCount = $table.find('tbody').find('tr').size();
+                       // prompt output
+                       var nodeText = "node";
+                       var verb = "is";
+                       if (nodeCount != 1) {
+                               nodeText += "s";
+                               verb = "are";
+                       }
+                       var out = "There "+verb+" "+nodeCount+" "+nodeText;
+                       $p = $(document.createElement('p'));
+                       $p.append(out);
+                       $dashlet.append($p);
+            // add to dashlet
+            $dashlet.append($table);
+        });
+    },
+    ajax : {
+        dashlet : function(callback) {
+            $.getJSON(one.f.address.root+one.f.address.flows.flows, function(data) {
+                var body = one.f.nodes.data.dashlet(data);
+                var $body = one.f.nodes.body.dashlet(body, callback);
+                callback($body);
+            });
+        }
+    },
+    data : {
+        dashlet : function(data) {
+            var body = [];
+            $.each(data, function(key, value) {
+                var tr = {};
+                var entry = [];
+                entry.push(key);
+                // parse ports
+                entry.push(value);
+                // add entry to tr
+                tr['entry'] = entry;
+                body.push(tr);
+            });
+            return body;
+        }
+    },
+    body : {
+        dashlet : function(body, callback) {
+            var attributes = ['table-striped', 'table-bordered', 'table-hover', 'table-condensed'];
+            var $table = one.lib.dashlet.table.table(attributes);
+            
+            var headers = ['Node', 'Flows'];
+            var $thead = one.lib.dashlet.table.header(headers);
+            $table.append($thead);
+            
+            var $tbody = one.lib.dashlet.table.body(body);
+            $table.append($tbody);
+            
+            return $table;
+        }
+    }
+}
+
+/** FLOW DETAIL **/
+one.f.detail = {
+    id : {},
+    registry : {},
+    dashlet : function($dashlet, details) {
+        var $h4 = one.lib.dashlet.header("Flow Details");
+        $dashlet.append($h4);
+        
+        // details
+        if (details == undefined) {
+               var $none = $(document.createElement('div'));
+               $none.addClass('none');
+            var $p = $(document.createElement('p'));
+            $p.text('Please select a flow');
+            $p.addClass('text-center').addClass('text-info');
+            
+            $dashlet.append($none)
+               .append($p);
+        }
+    },
+    data : {
+               dashlet : function(data) {
+                       var body = [];
+                       var tr = {};
+                       var entry = [];
+
+                       entry.push(data['name']);
+                       entry.push(data['node']);
+                       entry.push(data['flow']['priority']);
+                       entry.push(data['flow']['hardTimeout']);
+                       entry.push(data['flow']['idleTimeout']);
+
+                       tr.entry = entry;
+                       body.push(tr);
+                       return body;
+               },
+        description : function(data) {
+                       var body = [];
+                       var tr = {};
+                       var entry = [];
+
+                       entry.push(data['flow']['etherType']);
+                       entry.push(data['flow']['vlanId']);
+                       entry.push(data['flow']['vlanPriority']);
+                       entry.push(data['flow']['srcMac']);
+                       entry.push(data['flow']['dstMac']);
+                       entry.push(data['flow']['srcIp']);
+                       entry.push(data['flow']['dstIp']);
+                       entry.push(data['flow']['tosBits']);
+                       entry.push(data['flow']['srcPort']);
+                       entry.push(data['flow']['dstPort']);
+                       entry.push(data['flow']['protocol']);
+                       entry.push(data['flow']['cookie']);
+
+                       tr.entry = entry;
+                       body.push(tr);
+                       return body;
+        },
+               actions : function(data) {
+                       var body = [];
+                       var tr = {};
+                       var entry = [];
+
+                       var actions = '';
+                       $(data['flow']['actions']).each(function(index, value) {
+                               actions += value + ', ';
+                       });
+                       actions = actions.slice(0,-2);
+                       entry.push(actions);
+
+                       tr.entry = entry;
+                       body.push(tr);
+                       return body;
+               }
+    },
+    body : {
+        dashlet : function(body) {
+                       // create table
+                       var header = ['Flow Name', 'Node', 'Priority', 'Hard Timeout', 'Idle Timeout'];
+                       var $thead = one.lib.dashlet.table.header(header);
+                       var attributes = ['table-striped', 'table-bordered', 'table-condensed'];
+                       var $table = one.lib.dashlet.table.table(attributes);
+                       $table.append($thead);
+
+                       var $tbody = one.lib.dashlet.table.body(body);
+                       $table.append($tbody);
+
+            return $table;
+        },
+               description : function(body) {
+                       var header = ['Ethernet Type', 'VLAN ID', 'VLAN Priority', 'Source MAC', 'Dest MAC', 'Source IP', 'Dest IP', 'TOS', 'Source Port', 'Dest Port', 'Protocol', 'Cookie'];
+                       var $thead = one.lib.dashlet.table.header(header);
+                       var attributes = ['table-striped', 'table-bordered', 'table-condensed'];
+                       var $table = one.lib.dashlet.table.table(attributes);
+                       $table.append($thead);
+
+                       var $tbody = one.lib.dashlet.table.body(body);
+                       $table.append($tbody);
+
+            return $table;
+               },
+               actions : function(body) {
+                       var header = ['Actions'];
+                       var $thead = one.lib.dashlet.table.header(header);
+                       var attributes = ['table-striped', 'table-bordered', 'table-condensed'];
+                       var $table = one.lib.dashlet.table.table(attributes);
+                       $table.append($thead);
+
+                       var $tbody = one.lib.dashlet.table.body(body);
+                       $table.append($tbody);
+
+            return $table;
+               }
+    }
+}
+
+/** FLOW ENTRIES **/
+one.f.flows = {
+    id : {
+        dashlet : {
+            add : "one_f_flows_id_dashlet_add",
+            remove : "one_f_flows_id_dashlet_remove",
+            toggle : "one_f_flows_id_dashlet_toggle"
+        },
+        modal : {
+                       install : "one_f_flows_id_modal_install",
+            add : "one_f_flows_id_modal_add",
+            close : "one_f_flows_id_modal_close",
+            modal : "one_f_flows_id_modal_modal",
+            dialog : {
+               modal : "one_f_flows_id_modal_dialog_modal",
+                remove : "one_f_flows_id_modal_dialog_remove",
+                close : "one_f_flows_id_modal_dialog_close"
+            },
+            action : {
+                button : "one_f_flows_id_modal_action_button",
+                modal : "one_f_flows_id_modal_action_modal",
+                add : "one_f_flows_id_modal_action_add",
+                close : "one_f_flows_id_modal_action_close",
+                table : "one_f_flows_id_modal_action_table",
+                addOutputPorts : "one_f_flows_id_modal_action_addOutputPorts",
+                setVlanId : "one_f_flows_id_modal_action_setVlanId",
+                setVlanPriority : "one_f_flows_id_modal_action_setVlanPriority",
+                modifyDatalayerSourceAddress : "one_f_flows_id_modal_action_modifyDatalayerSourceAddress",
+                modifyDatalayerDestinationAddress : "one_f_flows_id_modal_action_modifyDatalayerDestinationAddress",
+                modifyNetworkSourceAddress : "one_f_flows_modal_action_modifyNetworkSourceAddress",
+                modifyNetworkDestinationAddress : "one_f_flows_modal_action_modifyNetworkDestinationAddress",
+                modifyTosBits : "one_f_flows_modal_action_modifyTosBits",
+                modifyTransportSourcePort : "one_f_flows_modal_action_modifyTransportSourcePort",
+                modifyTransportDestinationPort : "one_f_flows_modal_action_modifyTransportDestinationPort",
+                               modal : {
+                                       modal : "one_f_flows_modal_action_modal_modal",
+                                       remove : "one_f_flows_modal_action_modal_remove",
+                                       cancel : "one_f_flows_modal_action_modal_cancel"
+                               }
+            },
+            form : {
+                name : "one_f_flows_id_modal_form_name",
+                nodes : "one_f_flows_id_modal_form_nodes",
+                port : "one_f_flows_id_modal_form_port",
+                priority : "one_f_flows_id_modal_form_priority",
+                hardTimeout : "one_f_flows_id_modal_form_hardTimeout",
+                               idleTimeout : "one_f_flows_id_modal_form_idleTimeout",
+                               cookie : "one_f_flows_id_modal_form_cookie",
+                etherType : "one_f_flows_id_modal_form_etherType",
+                vlanId : "one_f_flows_id_modal_form_vlanId",
+                vlanPriority : "one_f_flows_id_modal_form_vlanPriority",
+                srcMac : "one_f_flows_id_modal_form_srcMac",
+                dstMac : "one_f_flows_id_modal_form_dstMac",
+                srcIp : "one_f_flows_id_modal_form_srcIp",
+                dstIp : "one_f_flows_id_modal_form_dstIp",
+                tosBits : "one_f_flows_id_modal_form_tosBits",
+                srcPort : "one_f_flows_id_modal_form_srcPort",
+                dstPort : "one_f_flows_id_modal_form_dstPort",
+                protocol : "one_f_flows_id_modal_form_protocol"
+            }
+        }
+    },
+    registry : {},
+    dashlet : function($dashlet, callback) {
+        var $h4 = one.lib.dashlet.header("Flow Entries");
+        
+        if (one.role < 2) {
+               var button = one.lib.dashlet.button.single("Add Flow Entry", one.f.flows.id.dashlet.add, "btn-primary", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               
+               $button.click(function() {
+                   var $modal = one.f.flows.modal.initialize();
+                   $modal.modal();
+               });
+        }
+        
+        $dashlet.append($h4);
+        if (one.role < 2) $dashlet.append($button);
+        
+        // load body
+        one.f.flows.ajax.dashlet(function($table) {
+                       // total flows
+                       var flowCount = $table.find('tbody').find('tr').size();
+                       // prompt output
+                       var flowText = "flow";
+                       var verb = "is";
+                       if (flowCount != 1) {
+                               flowText += "s";
+                               verb = "are";
+                       }
+                       var out = "There "+verb+" "+flowCount+" "+flowText;
+                       $p = $(document.createElement('p'));
+                       $p.append(out);
+                       $dashlet.append($p);
+            // table bindings
+            $table.find('tbody').find('tr').click(function() {
+                var id = $($(this).find('td')[0]).text();
+                               var node = $(this).data('id');
+                one.f.flows.detail(id, node);
+            });
+            // add to dashlet
+            $dashlet.append($table);
+            // details callback
+            if(callback != undefined) callback();
+        });
+    },
+    detail : function(id, node) {
+        // clear flow details
+        var $detailDashlet = one.main.dashlet.right.bottom;
+        $detailDashlet.empty();
+        var $h4 = one.lib.dashlet.header("Flow Overview");
+        $detailDashlet.append($h4);
+        
+        // details
+        var flows = one.f.flows.registry.flows;
+        var flow;
+        $(flows).each(function(index, value) {
+            if (value['name'] == id) {
+                flow = value;
+            }
+        });
+        if (one.role < 2) {
+               // remove button
+               var button = one.lib.dashlet.button.single("Remove Flow", one.f.flows.id.dashlet.remove, "btn-danger", "btn-mini");
+               var $button = one.lib.dashlet.button.button(button);
+               $button.click(function() {
+                   var $modal = one.f.flows.modal.dialog.initialize(id, node);
+                   $modal.modal();
+               });
+               // toggle button
+               var toggle;
+               if (flow['flow']['installInHw'] == 'true') {
+                   toggle = one.lib.dashlet.button.single("Uninstall Flow", one.f.flows.id.dashlet.toggle, "btn-warning", "btn-mini");
+               } else {
+                   toggle = one.lib.dashlet.button.single("Install Flow", one.f.flows.id.dashlet.toggle, "btn-success", "btn-mini");
+               }
+               var $toggle = one.lib.dashlet.button.button(toggle);
+               $toggle.click(function() {
+                   one.f.flows.modal.ajax.toggleflow(id, node, function(data) {
+                       if(data == "Success") {
+                           one.main.dashlet.right.bottom.empty();
+                           one.f.detail.dashlet(one.main.dashlet.right.bottom);
+                           one.main.dashlet.left.top.empty();
+                               one.f.flows.dashlet(one.main.dashlet.left.top, function() {
+                                  // checks are backwards due to stale registry
+                                  if(flow['flow']['installInHw'] == 'true') {
+                                      one.lib.alert('Uninstalled Flow');
+                                  } else {
+                                      one.lib.alert('Installed Flow');
+                                  }
+                                  one.f.flows.detail(id, node)
+                               });
+                       } else {
+                           one.lib.alert('Cannot toggle flow: '+data);
+                       }
+                   });
+               });
+        }
+        // append details
+        var body = one.f.detail.data.dashlet(flow);
+        var $body = one.f.detail.body.dashlet(body);
+        if (one.role < 2) $detailDashlet.append($button).append($toggle);
+        $detailDashlet.append($body);
+               var body = one.f.detail.data.description(flow);
+               var $body = one.f.detail.body.description(body);
+               $detailDashlet.append($body);
+               var body = one.f.detail.data.actions(flow);
+               var $body = one.f.detail.body.actions(body);
+               $detailDashlet.append($body);
+    },
+    modal : {
+        dialog : {
+            initialize : function(id, node) {
+                var h3 = "Remove Flow?";
+                var $p = one.f.flows.modal.dialog.body(id);
+                var footer = one.f.flows.modal.dialog.footer();
+                var $modal = one.lib.modal.spawn(one.f.flows.id.modal.dialog.modal, h3, $p, footer);
+                $('#'+one.f.flows.id.modal.dialog.close, $modal).click(function() {
+                    $modal.modal('hide');
+                });
+                $('#'+one.f.flows.id.modal.dialog.remove, $modal).click(function() {
+                    one.f.flows.modal.ajax.removeflow(id, node, function(data) {
+                        if (data == "Success") {
+                            $modal.modal('hide');
+                            one.main.dashlet.right.bottom.empty();
+                            one.f.detail.dashlet(one.main.dashlet.right.bottom);
+                            one.main.dashlet.left.top.empty();
+                            one.f.flows.dashlet(one.main.dashlet.left.top);
+                            one.lib.alert('Flow removed');
+                        } else {
+                            one.lib.alert('Cannot remove flow: '+data);
+                        }
+                    });
+                });
+                return $modal;
+            },
+            footer : function() {
+                var footer = [];
+                
+                var removeButton = one.lib.dashlet.button.single("Remove Flow", one.f.flows.id.modal.dialog.remove, "btn-danger", "");
+                var $removeButton = one.lib.dashlet.button.button(removeButton);
+                footer.push($removeButton);
+                
+                var closeButton = one.lib.dashlet.button.single("Cancel", one.f.flows.id.modal.dialog.close, "", "");
+                var $closeButton = one.lib.dashlet.button.button(closeButton);
+                footer.push($closeButton);
+                
+                return footer;
+            },
+            body : function(id) {
+                var $p = $(document.createElement('p'));
+                $p.append('Remove flow '+id+'?');
+                return $p;
+            }
+        },
+        initialize : function() {
+            var h3 = "Add Flow Entry";
+            var footer = one.f.flows.modal.footer();
+            var $modal = one.lib.modal.spawn(one.f.flows.id.modal.modal, h3, "", footer);
+            
+            // bind close button
+            $('#'+one.f.flows.id.modal.close, $modal).click(function() {
+                $modal.modal('hide');
+            });
+            
+            // bind add flow button
+            $('#'+one.f.flows.id.modal.add, $modal).click(function() {
+                one.f.flows.modal.add($modal, 'false');
+            });
+
+            // bind install flow button
+            $('#'+one.f.flows.id.modal.install, $modal).click(function() {
+                one.f.flows.modal.add($modal, 'true');
+            });
+            
+            // inject body (nodePorts)
+            one.f.flows.modal.ajax.nodes(function(nodes, nodeports) {
+                var $body = one.f.flows.modal.body(nodes, nodeports);
+                one.lib.modal.inject.body($modal, $body);
+            });
+            
+            return $modal;
+        },
+        add : function($modal, install) {
+            var result = {};
+            
+            result['name'] = $('#'+one.f.flows.id.modal.form.name, $modal).val();
+            result['ingressPort'] = $('#'+one.f.flows.id.modal.form.port, $modal).val();
+            result['priority'] = $('#'+one.f.flows.id.modal.form.priority, $modal).val();
+            result['hardTimeout'] = $('#'+one.f.flows.id.modal.form.hardTimeout, $modal).val();
+                       result['idleTimeout'] = $('#'+one.f.flows.id.modal.form.idleTimeout, $modal).val();
+                       result['cookie'] = $('#'+one.f.flows.id.modal.form.cookie, $modal).val();
+            result['etherType'] = $('#'+one.f.flows.id.modal.form.etherType, $modal).val();
+            result['vlanId'] = $('#'+one.f.flows.id.modal.form.vlanId, $modal).val();
+            result['vlanPriority'] = $('#'+one.f.flows.id.modal.form.vlanPriority, $modal).val();
+            result['dlSrc'] = $('#'+one.f.flows.id.modal.form.srcMac, $modal).val();
+            result['dlDst'] = $('#'+one.f.flows.id.modal.form.dstMac, $modal).val();
+            result['nwSrc'] = $('#'+one.f.flows.id.modal.form.srcIp, $modal).val();
+            result['nwDst'] = $('#'+one.f.flows.id.modal.form.dstIp, $modal).val();
+            result['tosBits'] = $('#'+one.f.flows.id.modal.form.tosBits, $modal).val();
+            result['tpSrc'] = $('#'+one.f.flows.id.modal.form.srcPort, $modal).val();
+            result['tpDst'] = $('#'+one.f.flows.id.modal.form.dstPort, $modal).val();
+            result['protocol'] = $('#'+one.f.flows.id.modal.form.protocol, $modal).val();
+
+                       result['installInHw'] = install;
+
+            var nodeId = $('#'+one.f.flows.id.modal.form.nodes, $modal).val();
+            
+            $.each(result, function(key, value) {
+                if (value == "") delete result[key];
+            });
+            
+            var action = [];
+            var $table = $('#'+one.f.flows.id.modal.action.table, $modal);
+            $($table.find('tbody').find('tr')).each(function(index, value) {
+                               if (!$(this).find('td').hasClass('empty')) {
+                       action.push($(value).data('action'));
+                               }
+            });
+            result['actions'] = action;
+            
+            // frontend validation
+                       if (result['name'] == undefined) {
+                               alert('Need flow name');
+                               return;
+                       }
+                       if (nodeId == '') {
+                               alert('Select node');
+                               return;
+                       }
+                       if (result['ingressPort'] == undefined) {
+                               alert('Select port');
+                               return;
+                       }
+                       if (action.length == 0) {
+                               alert('Please specify an action');
+                               return;
+                       }
+            
+                       // package for ajax call
+            var resource = {};
+            resource['body'] = JSON.stringify(result);
+            resource['action'] = 'add';
+                       resource['nodeId'] = nodeId;
+            
+            one.f.flows.modal.ajax.saveflow(resource, function(data) {
+                if (data == "Success") {
+                    $modal.modal('hide');
+                    one.lib.alert('Flow added');
+                    one.main.dashlet.left.top.empty();
+                    one.f.flows.dashlet(one.main.dashlet.left.top);
+                } else {
+                                       alert('Could not add flow: '+data);
+                }
+            });
+        },
+        ajax : {
+            nodes : function(successCallback) {
+                $.getJSON(one.f.address.root+one.f.address.flows.nodes, function(data) {
+                    var nodes = one.f.flows.modal.data.nodes(data);
+                    var nodeports = data;
+                    one.f.flows.registry['nodeports'] = nodeports;
+                    
+                    successCallback(nodes, nodeports);
+                });
+            },
+            saveflow : function(resource, callback) {
+                $.post(one.f.address.root+one.f.address.flows.flow, resource, function(data) {
+                    callback(data);
+                });
+            },
+            removeflow : function(id, node, callback) {
+               resource = {};
+               resource['action'] = 'remove';
+                $.post(one.f.address.root+one.f.address.flows.flow+'/'+node+'/'+id, resource, function(data) {
+                    callback(data);
+                });
+            },
+            toggleflow : function(id, node, callback) {
+               resource = {};
+               resource['action'] = 'toggle';
+               $.post(one.f.address.root+one.f.address.flows.flow+'/'+node+'/'+id, resource, function(data) {
+                       callback(data);
+               });
+            }
+        },
+        data : {
+            nodes : function(data) {
+                result = {};
+                $.each(data, function(key, value) {
+                    result[key] = value['name'];
+                });
+                return result;
+            }
+        },
+        body : function(nodes, nodeports) {
+            var $form = $(document.createElement('form'));
+                       var $fieldset = $(document.createElement('fieldset'));
+                       // flow description
+                       var $legend = one.lib.form.legend("Flow Description");
+                       $fieldset.append($legend);
+                       // name
+                       var $label = one.lib.form.label("Name");
+                       var $input = one.lib.form.input("Match Name");
+                       $input.attr('id', one.f.flows.id.modal.form.name);
+                       $fieldset.append($label).append($input);
+                       // node
+                       var $label = one.lib.form.label("Node");
+                       var $select = one.lib.form.select.create(nodes);
+                       one.lib.form.select.prepend($select, { '' : 'Please Select a Node' });
+                       $select.val($select.find("option:first").val());
+                       $select.attr('id', one.f.flows.id.modal.form.nodes);
+                       
+                       // bind onchange
+                       $select.change(function() {
+                           // retrieve port value
+                           var node = $(this).find('option:selected').attr('value');
+                           var $ports = $('#'+one.f.flows.id.modal.form.port);
+                               if (node == '') {
+                                       one.lib.form.select.inject($ports, {});
+                                       return;
+                               }
+                           one.f.flows.registry['currentNode'] = node;
+                           var ports = nodeports[node]['ports'];
+                           one.lib.form.select.inject($ports, ports);
+                           one.lib.form.select.prepend($ports, { '' : 'Please Select a Port' });
+                           $ports.val($ports.find("option:first").val());
+                       });
+
+            $fieldset.append($label).append($select);
+                       // input port
+                       var $label = one.lib.form.label("Input Port");
+                       var $select = one.lib.form.select.create();
+                       $select.attr('id', one.f.flows.id.modal.form.port);
+                       $fieldset.append($label).append($select);
+                       // priority
+                       var $label = one.lib.form.label("Priority");
+                       var $input = one.lib.form.input("Priority");
+                       $input.attr('id', one.f.flows.id.modal.form.priority);
+                       $input.val('500');
+                       $fieldset.append($label).append($input);
+                       // hardTimeout
+                       var $label = one.lib.form.label("Hard Timeout");
+                       var $input = one.lib.form.input("Hard Timeout");
+                       $input.attr('id', one.f.flows.id.modal.form.hardTimeout);
+                       $fieldset.append($label).append($input);
+                       // idleTimeout
+                       var $label = one.lib.form.label("Idle Timeout");
+                       var $input = one.lib.form.input("Idle Timeout");
+                       $input.attr('id', one.f.flows.id.modal.form.idleTimeout);
+                       $fieldset.append($label).append($input);
+                       // cookie
+                       var $label = one.lib.form.label("Cookie");
+                       var $input = one.lib.form.input("Cookie");
+                       $input.attr('id', one.f.flows.id.modal.form.cookie);
+                       $fieldset.append($label).append($input);
+                       // layer 2
+                       var $legend = one.lib.form.legend("Layer 2");
+                       $fieldset.append($legend);
+                       // etherType
+                       var $label = one.lib.form.label("Ethernet Type");
+                       var $input = one.lib.form.input("Ethernet Type");
+                       $input.attr('id', one.f.flows.id.modal.form.etherType);
+                       $input.val('0x800');
+                       $fieldset.append($label).append($input);
+                       // vlanId
+                       var $label = one.lib.form.label("VLAN Identification Number");
+                       var $input = one.lib.form.input("VLAN Identification Number");
+                       $input.attr('id', one.f.flows.id.modal.form.vlanId);
+                       var $help = one.lib.form.help("Range: 0 - 4095");
+                       $fieldset.append($label).append($input).append($help);
+                       // vlanPriority
+                       var $label = one.lib.form.label("VLAN Priority");
+                       var $input = one.lib.form.input("VLAN Priority");
+                       $input.attr('id', one.f.flows.id.modal.form.vlanPriority);
+                       var $help = one.lib.form.help("Range: 0 - 7");
+                       $fieldset.append($label).append($input).append($help);
+                       // srcMac
+                       var $label = one.lib.form.label("Source MAC Address");
+                       var $input = one.lib.form.input("Source MAC Address");
+                       $input.attr('id', one.f.flows.id.modal.form.srcMac);
+                       var $help = one.lib.form.help("Example: 00:11:22:aa:bb:cc");
+                       $fieldset.append($label).append($input).append($help);
+                       // dstMac
+                       var $label = one.lib.form.label("Destination MAC Address");
+                       var $input = one.lib.form.input("Destination MAC Address");
+                       $input.attr('id', one.f.flows.id.modal.form.dstMac);
+                       var $help = one.lib.form.help("Example: 00:11:22:aa:bb:cc");
+                       $fieldset.append($label).append($input).append($help);
+                       // layer 3
+                       var $legend = one.lib.form.legend("Layer 3");
+                       $fieldset.append($legend);
+                       // srcIp
+                       var $label = one.lib.form.label("Source IP Address");
+                       var $input = one.lib.form.input("Source IP Address");
+                       $input.attr('id', one.f.flows.id.modal.form.srcIp);
+                       var $help = one.lib.form.help("Example: 127.0.0.1");
+                       $fieldset.append($label).append($input).append($help);
+                       // dstIp
+                       var $label = one.lib.form.label("Destination IP Address");
+                       var $input = one.lib.form.input("Destination IP Address");
+                       $input.attr('id', one.f.flows.id.modal.form.dstIp);
+                       var $help = one.lib.form.help("Example: 127.0.0.1");
+                       $fieldset.append($label).append($input).append($help);
+                       // tosBits
+                       var $label = one.lib.form.label("TOS Bits");
+                       var $input = one.lib.form.input("TOS Bits");
+                       $input.attr('id', one.f.flows.id.modal.form.tosBits);
+                       var $help = one.lib.form.help("Range: 0 - 63");
+                       $fieldset.append($label).append($input).append($help);
+                       // layer 4
+                       var $legend = one.lib.form.legend("Layer 4");
+                       $fieldset.append($legend);
+                       // srcPort
+                       var $label = one.lib.form.label("Source Port");
+                       var $input = one.lib.form.input("Source Port");
+                       $input.attr('id', one.f.flows.id.modal.form.srcPort);
+                       var $help = one.lib.form.help("Range: 1 - 65535");
+                       $fieldset.append($label).append($input).append($help);
+                       // dstPort
+                       var $label = one.lib.form.label("Destination Port");
+                       var $input = one.lib.form.input("Destination Port");
+                       $input.attr('id', one.f.flows.id.modal.form.dstPort);
+                       var $help = one.lib.form.help("Range: 1 - 65535");
+                       $fieldset.append($label).append($input).append($help);
+                       // protocol
+                       var $label = one.lib.form.label("Protocol");
+                       var $input = one.lib.form.input("Protocol");
+                       $input.attr('id', one.f.flows.id.modal.form.protocol);
+                       $fieldset.append($label).append($input);
+                       // actions
+                       var $legend = one.lib.form.label("Actions");
+                       $fieldset.append($legend);
+                       // actions table
+                       var tableAttributes = ["table-striped", "table-bordered", "table-condensed", "table-hover", "table-cursor"];
+                       var $table = one.lib.dashlet.table.table(tableAttributes);
+                       $table.attr('id', one.f.flows.id.modal.action.table);
+                       var tableHeaders = ["Action", "Data", "Type"];
+                   var $thead = one.lib.dashlet.table.header(tableHeaders);
+                       var $tbody = one.lib.dashlet.table.body("", tableHeaders);
+                       $table.append($thead).append($tbody);
+                       // actions
+                       var actions = {
+                           "" : "Please Select an Action",
+                           "drop" : "Drop",
+                           "loopback" : "Loopback",
+                           "flood" : "Flood",
+                           "softwarePath" : "Software Path",
+                           "hardwarePath" : "Hardware Path",
+                           "controller" : "Controller",
+                           "addOutputPorts" : "Add Output Ports",
+                           "setVlanId" : "Set VLAN ID",
+                           "setVlanPriority" : "Set VLAN Priority",
+                           "stripVlanHeader" : "Strip VLAN Header",
+                           "modifyDatalayerSourceAddress" : "Modify Datalayer Source Address",
+                           "modifyDatalayerDestinationAddress" : "Modify Datalayer Destination Address",
+                           "modifyNetworkSourceAddress" : "Modify Network Source Address",
+                           "modifyNetworkDestinationAddress" :"Modify Network Destination Address",
+                           "modifyTosBits" : "Modify TOS Bits",
+                           "modifyTransportSourcePort" : "Modify Transport Source Port",
+                           "modifyTransportDestinationPort" : "Modify Transport Destination Port"
+                       };
+            var $select = one.lib.form.select.create(actions);
+            // when selecting an action
+            $select.change(function() {
+                var action = $(this).find('option:selected');
+                one.f.flows.modal.action.parse(action.attr('value'));
+                               $select[0].selectedIndex = 0;
+            });
+            
+                       $fieldset.append($select).append($table);
+            
+                       // return
+                       $form.append($fieldset);
+                       return $form;
+        },
+        action : {
+            parse : function(option) {
+                switch (option) {
+                    case "addOutputPorts" :
+                        var h3 = "Add Output Port";
+                        var $modal = one.f.flows.modal.action.initialize(h3, one.f.flows.modal.action.body.addOutputPorts, one.f.flows.modal.action.add.addOutputPorts);
+                        $modal.modal();
+                        break;
+                    case "setVlanId" :
+                        var h3 = "Set VLAN ID";
+                        var placeholder = "VLAN Identification Number";
+                        var id = one.f.flows.id.modal.action.setVlanId;
+                        var help = "Range: 0 - 4095";
+                        var action = 'setVlan';
+                        var name = "VLAN ID";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "setVlanPriority" :
+                        var h3 = "Set VLAN Priority";
+                        var placeholder = "VLAN Priority";
+                        var id = one.f.flows.id.modal.action.setVlanPriority;
+                        var help = "Range: 0 - 7";
+                        var action = 'setVlanPcp';
+                        var name = "VLAN Priority";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "stripVlanHeader" :
+                        var name = "Strip VLAN Header";
+                        var action = 'stripVlan';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                    case "modifyDatalayerSourceAddress" :
+                        var h3 = "Set Source MAC Address";
+                        var placeholder = "Source MAC Address";
+                        var id = one.f.flows.id.modal.action.modifyDatalayerSourceAddress;
+                        var help = "Example: 00:11:22:aa:bb:cc";
+                        var action = 'setDlSrc';
+                        var name = "Source MAC";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "modifyDatalayerDestinationAddress" :
+                        var h3 = "Set Destination MAC Address";
+                        var placeholder = "Destination MAC Address";
+                        var id = one.f.flows.id.modal.action.modifyDatalayerDestinationAddress;
+                        var help = "Example: 00:11:22:aa:bb:cc";
+                        var action = 'setDlDst';
+                        var name = "Destination MAC";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "modifyNetworkSourceAddress" :
+                        var h3 = "Set IP Source Address";
+                        var placeholder = "Source IP Address";
+                        var id = one.f.flows.id.modal.action.modifyNetworkSourceAddress;
+                        var help = "Example: 127.0.0.1";
+                        var action = 'setNwSrc';
+                        var name = "Source IP";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "modifyNetworkDestinationAddress" :
+                        var h3 = "Set IP Destination Address";
+                        var placeholder = "Destination IP Address";
+                        var id = one.f.flows.id.modal.action.modifyNetworkDestinationAddress;
+                        var help = "Example: 127.0.0.1";
+                        var action = 'setNwDst';
+                        var name = "Destination IP";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "modifyTosBits" :
+                        var h3 = "Set IPv4 ToS";
+                        var placeholder = "IPv4 ToS";
+                        var id = one.f.flows.id.modal.action.modifyTosBits;
+                        var help = "Range: 0 - 63";
+                        var action = 'setNwTos';
+                        var name = "TOS Bits";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "modifyTransportSourcePort" :
+                        var h3 = "Set Transport Source Port";
+                        var placeholder = "Transport Source Port";
+                        var id = one.f.flows.id.modal.action.modifyTransportSourcePort;
+                        var help = "Range: 1 - 65535";
+                        var action = 'setTpSrc';
+                        var name = "Source Port";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "modifyTransportDestinationPort" :
+                        var h3 = "Set Transport Destination Port";
+                        var placeholder = "Transport Destination Port";
+                        var id = one.f.flows.id.modal.action.modifyTransportDestinationPort;
+                        var help = "Range: 1 - 65535";
+                        var action = 'setTpDst';
+                        var name = "Destination Port";
+                        var body = function() {
+                            return one.f.flows.modal.action.body.set(h3, placeholder, id, help);
+                        };
+                        var add = function($modal) {
+                            one.f.flows.modal.action.add.set(name, id, action, $modal);
+                        };
+                        var $modal = one.f.flows.modal.action.initialize(h3, body, add);
+                        $modal.modal();
+                        break;
+                    case "drop" :
+                        var name = "Drop";
+                        var action = 'drop';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                    case "loopback" :
+                        var name = "Loopback";
+                        var action = 'loopback';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                    case "flood" :
+                        var name = "Flood";
+                        var action = 'flood';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                    case "softwarePath" :
+                        var name = "Software Path";
+                        var action = 'software path';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                    case "hardwarePath" :
+                        var name = "Hardware Path";
+                        var action = 'hardware path';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                    case "controller" :
+                        var name = "Controller";
+                        var action = 'controller';
+                        one.f.flows.modal.action.add.add(name, action);
+                        break;
+                }
+            },
+            initialize : function(h3, bodyCallback, addCallback) {
+                var footer = one.f.flows.modal.action.footer();
+                var $body = bodyCallback();
+                var $modal = one.lib.modal.spawn(one.f.flows.id.modal.action.modal, h3, $body, footer);
+                // bind close button
+                $('#'+one.f.flows.id.modal.action.close, $modal).click(function() {
+                    $modal.modal('hide');
+                });
+                // bind add flow button
+                $('#'+one.f.flows.id.modal.action.add, $modal).click(function() {
+                    addCallback($modal);
+                });
+                return $modal;
+            },
+            add : {
+                addOutputPorts : function($modal) {
+                    var $options = $('#'+one.f.flows.id.modal.action.addOutputPorts).find('option:selected');
+                    var ports = '';
+                    var pid = '';
+                    $options.each(function(index, value) {
+                        ports = ports+$(value).text()+", ";
+                        pid = pid+$(value).attr('value')+",";
+                    });
+                    ports = ports.slice(0,-2);
+                    pid = pid.slice(0,-1);
+                    var $tr = one.f.flows.modal.action.table.add("Add Output Ports", ports);
+                    $tr.attr('id', 'addOutputPorts');
+                    $tr.data('action', 'OUTPUT='+pid);
+                                       $tr.click(function() {
+                                               one.f.flows.modal.action.add.modal.initialize(this);
+                                       });
+                    one.f.flows.modal.action.table.append($tr);
+                    $modal.modal('hide');
+                },
+                add : function(name, action) {
+                    var $tr = one.f.flows.modal.action.table.add(name);
+                    $tr.attr('id', action);
+                    $tr.data('action', action);
+                                       $tr.click(function() {
+                                               one.f.flows.modal.action.add.modal.initialize(this);
+                                       });
+                    one.f.flows.modal.action.table.append($tr);
+                },
+                set : function(name, id, action, $modal) {
+                    var $input = $('#'+id);
+                    var value = $input.val();
+                    var $tr = one.f.flows.modal.action.table.add(name, value);
+                    $tr.attr('id', action);
+                    $tr.data('action', action+'='+value);
+                                       $tr.click(function() {
+                                               one.f.flows.modal.action.add.modal.initialize(this);
+                                       });
+                    one.f.flows.modal.action.table.append($tr);
+                    $modal.modal('hide');
+                },
+                               remove : function(that) {
+                                       $(that).remove();
+                                       var $table = $('#'+one.f.flows.id.modal.action.table);
+                                       if ($table.find('tbody').find('tr').size() == 0) {
+                                               var $tr = $(document.createElement('tr'));
+                                               var $td = $(document.createElement('td'));
+                                               $td.attr('colspan', '3');
+                                               $tr.addClass('empty');
+                                               $td.text('No data available');
+                                               $tr.append($td);
+                                               $table.find('tbody').append($tr);
+                                       }
+                               },
+                               modal : {
+                                       initialize : function(that) {
+                                               var h3 = "Remove Action";
+                                               var footer = one.f.flows.modal.action.add.modal.footer();
+                                               var $body = one.f.flows.modal.action.add.modal.body();
+                                               var $modal = one.lib.modal.spawn(one.f.flows.id.modal.action.modal.modal, h3, $body, footer);
+
+                                               // bind cancel button
+                                               $('#'+one.f.flows.id.modal.action.modal.cancel, $modal).click(function() {
+                                                       $modal.modal('hide');
+                                               });
+
+                                               // bind remove button
+                                               $('#'+one.f.flows.id.modal.action.modal.remove, $modal).click(function() {
+                                                       one.f.flows.modal.action.add.remove(that);
+                                                       $modal.modal('hide');
+                                               });
+
+                                               $modal.modal();
+                                       },
+                                       body : function() {
+                                               var $p = $(document.createElement('p'));
+                                               $p.append("Remove this action?");
+                                               return $p;
+                                       },
+                                       footer : function() {
+                                               var footer = [];
+
+                                               var removeButton = one.lib.dashlet.button.single("Remove Action", one.f.flows.id.modal.action.modal.remove, "btn-danger", "");
+                                               var $removeButton = one.lib.dashlet.button.button(removeButton);
+                                               footer.push($removeButton);
+
+                                               var cancelButton = one.lib.dashlet.button.single("Cancel", one.f.flows.id.modal.action.modal.cancel, "", "");
+                                               var $cancelButton = one.lib.dashlet.button.button(cancelButton);
+                                               footer.push($cancelButton);
+
+                                               return footer;
+                                       }
+                               }
+            },
+            table : {
+                add : function(action, data, type) {
+                    var $tr = $(document.createElement('tr'));
+                    var $td = $(document.createElement('td'));
+                    $td.append(action);
+                    $tr.append($td);
+                    var $td = $(document.createElement('td'));
+                    if (data != undefined) $td.append(data);
+                    $tr.append($td);
+                    var $td = $(document.createElement('td'));
+                    if (type != undefined) $td.append(type);
+                    $tr.append($td);
+                    return $tr;
+                },
+                append : function($tr) {
+                    var $table = $('#'+one.f.flows.id.modal.action.table);
+                    var $empty = $table.find('.empty').parent();
+                    if ($empty.size() > 0) $empty.remove();
+                    $table.append($tr);
+                }
+            },
+            body : {
+                common : function() {
+                    var $form = $(document.createElement('form'));
+                    var $fieldset = $(document.createElement('fieldset'));
+                    return [$form, $fieldset];
+                },
+                addOutputPorts : function() {
+                    var common = one.f.flows.modal.action.body.common();
+                    var $form = common[0];
+                    var $fieldset = common[1];
+                    // output port
+                    $label = one.lib.form.label("Select Output Ports");
+                    var ports = one.f.flows.registry.nodeports[one.f.flows.registry.currentNode]['ports'];
+                    $select = one.lib.form.select.create(ports, true);
+                    $select.attr('id', one.f.flows.id.modal.action.addOutputPorts);
+                    one.lib.form.select.prepend($select, {'':'Select a Port'});
+                    $fieldset.append($label).append($select);
+                    $form.append($fieldset);
+                    return $form;
+                },
+                set : function(label, placeholder, id, help) {
+                    var common = one.f.flows.modal.action.body.common();
+                    var $form = common[0];
+                    var $fieldset = common[1];
+                    // input
+                    $label = one.lib.form.label(label);
+                    $input = one.lib.form.input(placeholder);
+                    $input.attr('id', id);
+                    $help = one.lib.form.help(help);
+                    // append
+                    $fieldset.append($label).append($input).append($help);
+                    $form.append($fieldset);
+                    return $form;
+                }
+            },
+            footer : function() {
+                var footer = [];
+                var addButton = one.lib.dashlet.button.single("Add Action", one.f.flows.id.modal.action.add, "btn-primary", "");
+                var $addButton = one.lib.dashlet.button.button(addButton);
+                footer.push($addButton);
+                
+                var closeButton = one.lib.dashlet.button.single("Close", one.f.flows.id.modal.action.close, "", "");
+                var $closeButton = one.lib.dashlet.button.button(closeButton);
+                footer.push($closeButton);
+                
+                return footer;
+            }
+        },
+        footer : function() {
+            var footer = [];
+
+                       var installButton = one.lib.dashlet.button.single("Install Flow", one.f.flows.id.modal.install, "btn-success", "");
+                       var $installButton = one.lib.dashlet.button.button(installButton);
+                       footer.push($installButton);
+            
+            var addButton = one.lib.dashlet.button.single("Save Flow", one.f.flows.id.modal.add, "btn-primary", "");
+            var $addButton = one.lib.dashlet.button.button(addButton);
+            footer.push($addButton);
+            
+            var closeButton = one.lib.dashlet.button.single("Close", one.f.flows.id.modal.close, "", "");
+            var $closeButton = one.lib.dashlet.button.button(closeButton);
+            footer.push($closeButton);
+            
+            return footer;
+        }
+    },
+    ajax : {
+        dashlet : function(callback) {
+            $.getJSON(one.f.address.root+one.f.address.flows.main, function(data) {
+                one.f.flows.registry['flows'] = data;
+                var body = one.f.flows.data.dashlet(data);
+                var $body = one.f.flows.body.dashlet(body, callback);
+                callback($body);
+            });
+        }
+    },
+    data : {
+        dashlet : function(data) {
+            var body = [];
+            $(data).each(function(index, value) {
+                var tr = {};
+                var entry = [];
+                entry.push(value['name']);
+                entry.push(value['node']);
+                if (value['flow']['installInHw'] == 'true')
+                       tr['type'] = ['success'];
+                else if (value['flow']['installInHw'] == 'false')
+                       tr['type'] = ['warning'];
+                tr['entry'] = entry;
+                tr['id'] = value['nodeId'];
+                
+                body.push(tr);
+            });
+            return body;
+        }
+    },
+    body : {
+        dashlet : function(body, callback) {
+            var attributes = ['table-striped', 'table-bordered', 'table-hover', 'table-condensed', 'table-cursor'];
+            var $table = one.lib.dashlet.table.table(attributes);
+            
+            var headers = ['Flow Name', 'Node'];
+            var $thead = one.lib.dashlet.table.header(headers);
+            $table.append($thead);
+            
+            var $tbody = one.lib.dashlet.table.body(body);
+            $table.append($tbody);
+            
+            return $table;
+        }
+    }
+}
+
+/** INIT **/
+// populate nav tabs
+$(one.f.menu.left.top).each(function(index, value) {
+    var $nav = $(".nav", "#left-top");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.left.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#left-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.right.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#right-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+one.f.populate = function($dashlet, header) {
+    var $h4 = one.lib.dashlet.header(header);
+    $dashlet.append($h4);
+};
+
+// bind dashlet nav
+$('.dash .nav a', '#main').click(function() {
+    // de/activation
+    var $li = $(this).parent();
+    var $ul = $li.parent();
+    one.lib.nav.unfocus($ul);
+    $li.addClass('active');
+    // clear respective dashlet
+    var $dashlet = $ul.parent().find('.dashlet');
+    one.lib.dashlet.empty($dashlet);
+    // callback based on menu
+    var id = $(this).attr('id');
+    var menu = one.f.dashlet;
+    switch (id) {
+        case menu.flows.id:
+            one.f.flows.dashlet($dashlet);
+            break;
+        case menu.nodes.id:
+            one.f.nodes.dashlet($dashlet);
+            break;
+        case menu.detail.id:
+            one.f.detail.dashlet($dashlet);
+            break;
+    };
+});
+
+// activate first tab on each dashlet
+$('.dash .nav').each(function(index, value) {
+    $($(value).find('li')[0]).find('a').click();
+});
diff --git a/opendaylight/web/root/pom.xml b/opendaylight/web/root/pom.xml
new file mode 100644 (file)
index 0000000..7c11554
--- /dev/null
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../../commons/opendaylight</relativePath>
+       </parent>
+
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>web</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.configuration,
+                                                       org.opendaylight.controller.sal.authorization,
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.usermanager,
+                                                       org.opendaylight.controller.usermanager.internal,
+                                                       org.opendaylight.controller.containermanager,
+                                                       com.google.gson,
+                                                       javax.annotation,
+                                                       javax.naming,
+                                                       javax.servlet,
+                                                       javax.servlet.annotation,
+                                                       javax.servlet.http,
+                                                       javax.servlet.jsp,
+                                                       javax.servlet.jsp.el,
+                                                       javax.servlet.jsp.jstl.core,
+                                                       javax.servlet.jsp.jstl.fmt,
+                                                       javax.servlet.jsp.jstl.tlv,
+                                                       javax.servlet.jsp.tagext,
+                                                       javax.servlet.resources,
+                                                       javax.xml.parsers,
+                                                       javax.xml.transform,
+                                                       org.apache.commons.logging,
+                                                       org.apache.taglibs.standard.functions,
+                                                       org.apache.taglibs.standard.resources,
+                                                       org.apache.taglibs.standard.tag.common.core,
+                                                       org.apache.taglibs.standard.tag.common.fmt,
+                                                       org.apache.taglibs.standard.tag.rt.core,
+                                                       org.apache.taglibs.standard.tag.rt.fmt,
+                                                       org.apache.taglibs.standard.tei,
+                                                       org.apache.taglibs.standard.tlv,
+                                                       org.codehaus.jackson,
+                                                       org.codehaus.jackson.annotate,
+                                                       org.codehaus.jackson.map,
+                                                       org.codehaus.jackson.map.annotate,
+                                                       org.osgi.framework,
+                                                       org.slf4j,
+                                                       org.springframework.beans,
+                                                       org.springframework.beans.factory.xml,
+                                                       org.springframework.context.config,
+                                                       org.springframework.core,
+                                                       org.springframework.stereotype,
+                                                       org.springframework.ui,
+                                                       org.springframework.web,
+                                                       org.springframework.web.bind.annotation,
+                                                       org.springframework.web.servlet,
+                                                       org.springframework.web.servlet.config,
+                                                       org.springframework.web.servlet.view,
+                                                       org.springframework.web.servlet.view.json,
+
+                                                       org.springframework.web.filter,
+                                                       org.springframework.web.context,
+                                                       org.springframework.security.core,
+                                                       org.springframework.security.core.userdetails,
+                                                       org.springframework.security.core.authority,
+                                                       org.springframework.security.core.context,
+                                                       org.springframework.security.authentication,
+                                                       org.springframework.security.config,
+                                                       org.springframework.security.config.authentication,
+                                                       org.springframework.security.taglibs.authz,
+                                                       org.springframework.security.web,
+                                                       org.springframework.security.web.context,
+                                                       org.springframework.security.web.authentication,
+                                                       org.springframework.security.web.authentication.www,
+                                                       org.springframework.security.provisioning,
+                                                       org.springframework.security.web.util,
+                                                       org.springframework.security.web.authentication.rememberme,
+                                                       org.springframework.security.web.authentication.logout,
+                                                       org.springframework.dao,
+                                                       org.springframework.security.web.savedrequest,
+                                                       org.springframework.security.access,
+                                                       org.springframework.util
+
+
+                                               </Import-Package>
+                                               <Export-Package>
+                                                       org.opendaylight.controller.web
+                                               </Export-Package>
+                                               <Web-ContextPath>/</Web-ContextPath>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>configuration</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>usermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>containermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>junit</groupId>
+                       <artifactId>junit</artifactId>
+                       <version>4.8.1</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-beans</artifactId>
+                       <version>3.2.1.RELEASE</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-core</artifactId>
+                       <version>3.2.1.RELEASE</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-context</artifactId>
+                       <version>3.2.1.RELEASE</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-expression</artifactId>
+                       <version>3.2.1.RELEASE</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-web</artifactId>
+                       <version>3.2.1.RELEASE</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-webmvc</artifactId>
+                       <version>3.2.1.RELEASE</version>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/AuthenticationProviderWrapper.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/AuthenticationProviderWrapper.java
new file mode 100644 (file)
index 0000000..7877ff6
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+
+public class AuthenticationProviderWrapper implements
+        AuthenticationProvider {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(AuthenticationProviderWrapper.class);
+
+    @Override
+    public Authentication authenticate(Authentication authentication)
+            throws AuthenticationException {
+        return ((AuthenticationProvider) getUserManagerRef())
+                .authenticate(authentication);
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return ((AuthenticationProvider) getUserManagerRef())
+                .supports(authentication);
+    }
+
+    private IUserManager getUserManagerRef() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager != null) {
+            return userManager;
+        } else {
+            logger.error("UserManager Ref is null. ");
+            throw new RuntimeException("UserManager Ref is null. ");
+        }
+    }
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerAuthenticationSuccessHandler.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerAuthenticationSuccessHandler.java
new file mode 100644 (file)
index 0000000..9514109
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.web;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.security.web.savedrequest.SavedRequest;
+import org.springframework.util.StringUtils;
+
+public class ControllerAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
+    private RequestCache requestCache = new HttpSessionRequestCache();
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+            Authentication authentication) throws ServletException, IOException {
+        SavedRequest savedRequest = requestCache.getRequest(request, response);
+
+        if (savedRequest == null) {
+            super.onAuthenticationSuccess(request, response, authentication);
+
+            return;
+        }
+        String targetUrlParameter = getTargetUrlParameter();
+        if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
+            requestCache.removeRequest(request, response);
+            super.onAuthenticationSuccess(request, response, authentication);
+
+            return;
+        }
+
+        clearAuthenticationAttributes(request);
+
+        // Use the DefaultSavedRequest URL
+        
+        String targetUrl = savedRequest.getRedirectUrl();
+        //workaround to avoid being redirected to ajax calls
+        Map<String, String[]> m = savedRequest.getParameterMap();
+        if(m!= null)
+        {
+            String[] value = m.get("x-page-url");
+            if(value != null && value.length > 0)
+                targetUrl = request.getContextPath() + "#" + value[0];
+        }
+        logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
+        
+        
+        
+        getRedirectStrategy().sendRedirect(request, response, targetUrl);
+    }
+
+    public void setRequestCache(RequestCache requestCache) {
+        this.requestCache = requestCache;
+    }
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerCustomFilter.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerCustomFilter.java
new file mode 100644 (file)
index 0000000..a14df94
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.web;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+public class ControllerCustomFilter extends GenericFilterBean {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(ControllerCustomFilter.class);
+
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse resp,
+            FilterChain chain) throws IOException, ServletException {
+        //custom filter to handle logged out users
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) resp;
+
+        String url = request.getRequestURL().toString();
+
+        //skip anonymous auth
+        if (!(url.indexOf("login") > -1) && !(url.indexOf("logout") > -1)) {
+            if (SecurityContextHolder.getContext().getAuthentication() != null
+                    && SecurityContextHolder.getContext().getAuthentication()
+                            .isAuthenticated()) {
+
+                IUserManager userManager = (IUserManager) ServiceHelper
+                        .getGlobalInstance(IUserManager.class, this);
+                if (userManager != null) {
+                    Map<String, List<String>> activeUsers = userManager
+                            .getUserLoggedIn();
+                    if (activeUsers != null && activeUsers.size() > 0) {
+
+                        String username = SecurityContextHolder.getContext()
+                                .getAuthentication().getName();
+                        if (!activeUsers.containsKey(username)) {
+                            throw new AccessDeniedException(
+                                    "UserManager activeUserList does not contain user "
+                                            + username);
+                        }
+                    } else {
+                        logger.error("UserManager return empty activeusers");
+                        throw new AccessDeniedException(
+                                "UserManager activeUserList is empty. ");
+                    }
+                } else {
+                    logger.error("UserManager Ref is null. ");
+                    throw new RuntimeException("UserManager Ref is null. ");
+                }
+
+            } else {
+                logger.error("SecurityContextHolder getAuthentication is null");
+                throw new AccessDeniedException(
+                        "SecurityContextHolder is not populated");
+            }
+        }
+
+        chain.doFilter(request, response);
+    }
+
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerLoginUrlAuthEntryPoint.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerLoginUrlAuthEntryPoint.java
new file mode 100644 (file)
index 0000000..36a192e
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.DefaultRedirectStrategy;
+import org.springframework.security.web.RedirectStrategy;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
+import org.springframework.security.web.util.RedirectUrlBuilder;
+
+@SuppressWarnings("deprecation")
+public class ControllerLoginUrlAuthEntryPoint extends
+        LoginUrlAuthenticationEntryPoint {
+
+    private String loginFormUrl = "/login";
+    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+
+    //This entry point always re-directs to root login page.
+
+   @Override
+   public void commence(HttpServletRequest request,
+            HttpServletResponse response, AuthenticationException authException)
+            throws IOException, ServletException {
+
+        String redirectUrl = request.getRequestURL().toString();
+            RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();
+            urlBuilder.setScheme(request.getScheme());
+            urlBuilder.setServerName(request.getServerName());
+            urlBuilder.setPort(getPortResolver().getServerPort(request));
+            // urlBuilder.setContextPath(request.getContextPath());
+            urlBuilder.setPathInfo(loginFormUrl);
+            redirectUrl = urlBuilder.getUrl();
+            redirectStrategy.sendRedirect(request, response, redirectUrl);  
+
+    }
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerLogoutHandler.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerLogoutHandler.java
new file mode 100644 (file)
index 0000000..ee06a13
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.web;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+public class ControllerLogoutHandler implements LogoutHandler {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(ControllerLogoutHandler.class);
+
+    @Override
+    public void logout(HttpServletRequest request,
+            HttpServletResponse response, Authentication authentication) {
+        if (authentication != null) {
+            String userName = authentication.getName();
+            if (userName != null) {
+                IUserManager userManager = (IUserManager) ServiceHelper
+                        .getGlobalInstance(IUserManager.class, this);
+                if (userManager != null) {
+                    userManager.userLogout(userName);
+                    HttpSession session = request.getSession();
+                    userManager.getSessionManager().invalidateSessions(userName, session.getId());
+                    
+                } else
+                    logger
+                            .error("UserMgr ref is null. Logout is not done cleanly");
+
+            } else
+                logger
+                        .error("User name is null in authentication. Logout is not done cleanly");
+        }
+
+    }
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerUISessionManager.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerUISessionManager.java
new file mode 100644 (file)
index 0000000..a5d76f2
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.web;
+
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControllerUISessionManager implements HttpSessionListener {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(ControllerUISessionManager.class);
+
+    @Override
+    public void sessionCreated(HttpSessionEvent se) {
+        ((HttpSessionListener) getUserManagerRef().getSessionManager())
+                .sessionCreated(se);
+    }
+
+    @Override
+    public void sessionDestroyed(HttpSessionEvent se) {
+        ((HttpSessionListener) getUserManagerRef().getSessionManager())
+                .sessionDestroyed(se);
+    }
+
+    private IUserManager getUserManagerRef() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager != null) {
+            return userManager;
+        } else {
+            logger.error("UserManager Ref is null. ");
+            throw new RuntimeException("UserManager Ref is null. ");
+        }
+    }
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerUserDetailsService.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerUserDetailsService.java
new file mode 100644 (file)
index 0000000..2aac0b0
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+
+public class ControllerUserDetailsService implements UserDetailsService {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(ControllerUserDetailsService.class);
+
+    ControllerUserDetailsService() {
+    }
+
+    @Override
+    public UserDetails loadUserByUsername(String username)
+            throws UsernameNotFoundException {
+        return getUserManagerRef().loadUserByUsername(username);
+    }
+
+    private IUserManager getUserManagerRef() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager != null) {
+            return userManager;
+        } else {
+            logger.error("UserManager Ref is null. ");
+            throw new RuntimeException("UserManager Ref is null. ");
+        }
+    }
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerWebSecurityContextRepository.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/ControllerWebSecurityContextRepository.java
new file mode 100644 (file)
index 0000000..38007f4
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+
+package org.opendaylight.controller.web;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.web.context.HttpRequestResponseHolder;
+import org.springframework.security.web.context.SecurityContextRepository;
+
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.usermanager.IUserManager;
+
+
+public class ControllerWebSecurityContextRepository implements
+        SecurityContextRepository {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(ControllerWebSecurityContextRepository.class);
+
+    ControllerWebSecurityContextRepository() {
+    }
+
+    @Override
+    public SecurityContext loadContext(
+            HttpRequestResponseHolder requestResponseHolder) {
+
+        SecurityContextRepository contextRepo = (SecurityContextRepository) getUserManagerRef()
+                .getSecurityContextRepo();
+        return contextRepo.loadContext(requestResponseHolder);
+    }
+
+    @Override
+    public void saveContext(SecurityContext context,
+            HttpServletRequest request, HttpServletResponse response) {
+        SecurityContextRepository contextRepo = (SecurityContextRepository) getUserManagerRef()
+                .getSecurityContextRepo();
+        contextRepo.saveContext(context, request, response);
+    }
+
+    private IUserManager getUserManagerRef() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager != null) {
+            return userManager;
+        } else {
+            logger.error("UserManager Ref is null. ");
+            throw new RuntimeException("UserManager Ref is null. ");
+        }
+    }
+
+    @Override
+    public boolean containsContext(HttpServletRequest request) {
+        SecurityContextRepository contextRepo = (SecurityContextRepository) getUserManagerRef()
+                .getSecurityContextRepo();
+        return contextRepo.containsContext(request);
+    }
+
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/IOneWeb.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/IOneWeb.java
new file mode 100644 (file)
index 0000000..72a3812
--- /dev/null
@@ -0,0 +1,26 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.web;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+
+/**
+ *
+ *
+ */
+public interface IOneWeb {
+    public String getWebName();
+
+    public String getWebId();
+
+    public short getWebOrder();
+    
+    public boolean isAuthorized(UserLevel userLevel);
+}
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/OneWeb.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/OneWeb.java
new file mode 100644 (file)
index 0000000..d1237c7
--- /dev/null
@@ -0,0 +1,103 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.opendaylight.controller.configuration.IConfigurationService;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping("/")
+public class OneWeb {
+    @RequestMapping(value = "")
+    public String index(Model model) {
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+               return "User Manager is not available";
+        }
+       
+        String username = SecurityContextHolder.getContext().getAuthentication().getName();
+        model.addAttribute("username", username);
+        model.addAttribute("role", userManager.getUserLevel(username).toNumber());
+        
+        return "main";
+    }
+
+    @RequestMapping(value = "web.json")
+    @ResponseBody
+    public Map<String, Map<String, Object>> bundles() {
+        Object[] instances = ServiceHelper.getGlobalInstances(IOneWeb.class,
+                this, null);
+        Map<String, Map<String, Object>> bundles = new HashMap<String, Map<String, Object>>();
+        Map<String, Object> entry;
+        IOneWeb bundle;
+        String userName = SecurityContextHolder.getContext().getAuthentication().getName();
+        IUserManager userManger = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
+        for (Object instance : instances) {
+            bundle = (IOneWeb) instance;
+            if (userManger != null &&
+                       bundle.isAuthorized(userManger.getUserLevel(userName))) {
+                   entry = new HashMap<String, Object>();
+                   entry.put("name", bundle.getWebName());
+                   entry.put("order", bundle.getWebOrder());
+                   bundles.put(bundle.getWebId(), entry);
+            }
+        }
+        return bundles;
+    }
+    
+    @RequestMapping(value = "save", method = RequestMethod.POST)
+    @ResponseBody
+    public String save() {
+       String username = SecurityContextHolder.getContext().getAuthentication().getName();
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) return "User Manager is not available";
+        
+        UserLevel level = userManager.getUserLevel(username);
+        if (level == UserLevel.NETWORKOPERATOR) {
+               return "Save not permitted for Operator";
+        }
+        
+        Status status = new Status(StatusCode.UNAUTHORIZED, 
+                       "Operation not allowed for current user");
+           if (level == UserLevel.NETWORKADMIN || level == UserLevel.SYSTEMADMIN) {
+               IConfigurationService configService = (IConfigurationService) ServiceHelper
+                       .getGlobalInstance(IConfigurationService.class, this);
+               if (configService != null) {
+                       status = configService.saveConfigurations();
+               }
+           }
+        
+        return status.getDescription();
+    }
+    
+    @RequestMapping(value = "login")
+       public String login(Map<String, Object> model, final HttpServletResponse response) {
+                response.setHeader("X-Page-Location", "/login");
+               return "login";
+       }
+
+}
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/OneWebAdmin.java b/opendaylight/web/root/src/main/java/org/opendaylight/controller/web/OneWebAdmin.java
new file mode 100644 (file)
index 0000000..a22d749
--- /dev/null
@@ -0,0 +1,106 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.web;
+
+import java.util.List;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.usermanager.internal.UserConfig;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.google.gson.Gson;
+
+@Controller
+@RequestMapping("/admin")
+public class OneWebAdmin {
+    @RequestMapping("/users")
+    @ResponseBody
+    public List<UserConfig> getUsers() {
+        IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+            return null;
+        }
+
+        List<UserConfig> userConfList = userManager.getLocalUserList();
+
+        return userConfList;
+    }
+
+    /*
+     * Password in clear text, moving to HTTP/SSL soon
+     */
+    @RequestMapping(value = "/users", method = RequestMethod.POST)
+    @ResponseBody
+    public String saveLocalUserConfig(
+            @RequestParam(required = true) String json,
+            @RequestParam(required = true) String action) {
+
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+               return "Internal Error";
+        }
+        
+        if (!authorize(userManager, UserLevel.NETWORKADMIN)) {
+                       return "Operation not permitted";
+        }
+       
+        Gson gson = new Gson();
+        UserConfig config = gson.fromJson(json, UserConfig.class);
+        
+        Status result = (action.equals("add")) ? 
+                       userManager.addLocalUser(config)
+                   : userManager.removeLocalUser(config);
+
+        return result.getDescription();
+    }
+    
+    @RequestMapping(value = "/users/{username}", method = RequestMethod.POST)
+    @ResponseBody
+    public String removeLocalUser(@PathVariable("username") String userName) {
+       if(SecurityContextHolder.getContext().getAuthentication()
+                       .getName().equals(userName)) {
+               return "Invalid Request: User cannot delete itself";
+       }
+       
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+               return "Internal Error";
+        }
+        
+        if (!authorize(userManager, UserLevel.NETWORKADMIN)) {
+                       return "Operation not permitted";
+        }
+        
+        return userManager.removeLocalUser(userName).getDescription();
+    }
+    
+    /**
+     * Is the operation permitted for the given level
+     * 
+     * @param level
+     */
+    private boolean authorize(IUserManager userManager, UserLevel level) {
+        String username = SecurityContextHolder.getContext().getAuthentication().getName();
+        UserLevel userLevel = userManager.getUserLevel(username);
+        return userLevel.toNumber() <= level.toNumber();
+    }
+}
diff --git a/opendaylight/web/root/src/main/resources/META-INF/spring.factories b/opendaylight/web/root/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/web/root/src/main/resources/META-INF/spring.handlers b/opendaylight/web/root/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/web/root/src/main/resources/META-INF/spring.schemas b/opendaylight/web/root/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/web/root/src/main/resources/META-INF/spring.tooling b/opendaylight/web/root/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/RootGUI-servlet.xml b/opendaylight/web/root/src/main/resources/WEB-INF/RootGUI-servlet.xml
new file mode 100644 (file)
index 0000000..a818c8e
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xmlns:mvc="http://www.springframework.org/schema/mvc"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <context:component-scan base-package="org.opendaylight.controller.web"/>
+  
+  <mvc:resources mapping="/js/**" location="/js/" />
+  <mvc:resources mapping="/css/**" location="/css/" />
+  <mvc:resources mapping="/img/**" location="/img/" />
+  <mvc:annotation-driven/>
+  
+  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+       <property name="prefix" value="/WEB-INF/jsp/"/>
+       <property name="suffix" value=".jsp"/>
+  </bean>
+</beans>
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/jsp/login.jsp b/opendaylight/web/root/src/main/resources/WEB-INF/jsp/login.jsp
new file mode 100644 (file)
index 0000000..a343d98
--- /dev/null
@@ -0,0 +1,48 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>OpenDaylight - Login</title>
+
+    <!-- Bootstrap CSS - 1 -->
+       <link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
+       
+       <!-- Login CSS - 2 -->
+       <link rel="stylesheet/less" type="text/css" href="/css/login.less">
+       
+       <!-- Bootstrap JS - 1 -->
+       <script src="/js/bootstrap.min.js"></script>
+       
+       <!-- LESS - 2 -->
+       <script type="text/javascript">
+               less = {
+                       env: "production"
+               };
+       </script>
+       <script src="/js/less-1.3.3.min.js"></script>
+</head>
+<body>
+        <form action="<c:url value='j_spring_security_check' />" id="form" method="post">
+
+  <div class="container">
+    <div class="content">
+       <div class="login-form">
+         <div id="logo"></div>
+           <fieldset>
+             <div class="control-group">
+               <input type="text" name="j_username" placeholder="Username">
+             </div>
+             <div class="control-group">
+               <input type="password" name="j_password" placeholder="Password">
+             </div>
+             <button class="btn btn-primary" type="submit" value="Log In" ><div class="icon-login"></div> Log In</button>
+           </fieldset>
+       </div>
+    </div>
+  </div> 
+  </form>
+</body>
+</html>
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp b/opendaylight/web/root/src/main/resources/WEB-INF/jsp/main.jsp
new file mode 100644 (file)
index 0000000..f217212
--- /dev/null
@@ -0,0 +1,154 @@
+<%--
+ - Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved. 
+ - 
+ - This program and the accompanying materials are made available under the 
+ - terms of the Eclipse Public License v1.0 which accompanies this distribution, 
+ - and is available at http://www.eclipse.org/legal/epl-v10.html
+--%>
+
+<!DOCTYPE html>
+<html>
+
+<head>
+       <title>OpenDaylight</title>
+       <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+       <!-- Bootstrap CSS - 1 -->
+       <link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
+
+       <!-- Core CSS - 2 -->
+       <link rel="stylesheet/less" type="text/css" href="/css/one.less">
+
+       <!-- jQuery - 1 -->
+       <script src="/js/jquery-1.9.1.min.js"></script>
+       
+       <!-- Bootstrap JS - 2 -->
+       <script src="/js/bootstrap.min.js"></script>
+
+       <!-- LESS - 3 -->
+       <script type="text/javascript">
+               less = {
+                       env: "production"
+               };
+       </script>
+       <script src="/js/less-1.3.3.min.js"></script>
+       
+       <!-- Topology - 4 -->
+       <script src="/js/jit.js"></script>
+</head>
+<body>
+
+<!-- #menu -->
+<div id="menu" class="navbar navbar-fixed-top">
+       <div class="navbar-inner row-fluid">
+               <div class="span10">
+                       <a class="brand" href="/" title="Version 0.1">OpenDaylight</a> 
+                       <ul class="nav nav-tabs">
+                       </ul>
+               </div>
+               <div class="span2">
+                       <div id="toolbar" class="btn-group">
+                               <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+                                       <div class="icon-user"></div> ${username}
+                                       <span class="caret"></span>
+                               </a>
+                               <ul class="dropdown-menu">
+                                       <li><a href="#admin" id="admin" data-role="${role}"><div class="icon-users"></div> Users</a></li>
+                                       <li><a href="#save" id="save"><div class="icon-save"></div> Save</a></li>
+                                       <li><a href="#logout" id="logout"><div class="icon-logout"></div> Logout</a></li>
+                               </ul>
+                       </div>
+               </div>
+       </div>
+</div><!-- END #menu -->
+
+<!-- #footer -->
+<div id="footer" class="navbar navbar-fixed-bottom">
+       <div class="navbar-inner row-fluid">
+               <div class="alert hide" id="alert">
+                       <button type="button" class="close">&times;</button>
+                       <p></p>
+               </div>
+       </div>
+</div><!-- END #footer -->
+
+<!-- #main -->
+<div id="main">
+       
+<!-- #left -->
+<div id="left">
+
+       <!-- #left-top -->
+       <div id="left-top">
+
+               <div class="dash">
+                       <ul class="nav nav-tabs">
+                       </ul>
+                       <div class="dashlet row-fluid">
+                       </div>
+               </div>
+
+       </div><!-- END #left-top -->
+
+       <!-- #left-bottom -->
+       <div id="left-bottom">
+
+               <div class="dash">
+                       <ul class="nav nav-tabs">
+                       </ul>
+                       <div class="dashlet row-fluid">
+                       </div>
+               </div>
+
+       </div><!-- END #left-bottom -->
+
+</div><!-- END #left -->
+
+<!-- #right -->
+<div id="right">
+
+       <!-- #right-top -->
+       <div id="right-top">
+
+               <div class="dash">
+                       <div id="topology"></div>
+               </div>
+
+       </div><!-- END #right-top -->
+
+       <!-- #right-bottom -->
+       <div id="right-bottom">
+
+               <div class="dash">
+                       <ul class="nav nav-tabs">
+                       </ul>
+                       <div class="dashlet row-fluid">
+                       </div>
+               </div>
+
+       </div><!-- END #right-bottom -->
+
+</div><!-- END #right -->
+
+</div><!-- END #main -->
+
+<!-- modal -->
+<div id="modal" class="modal hide fade">
+       <div class="modal-header">
+               <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+               <h3></h3>
+       </div>
+       <div class="modal-body"></div>
+       <div class="modal-footer"></div>
+</div>
+<!-- END modal -->
+
+<!-- Core JS - 6 -->
+<script src="/js/one.js"></script>
+
+<!-- Topology JS - 7 -->
+<script src="/js/one-topology.js"></script>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/web/root/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/web/root/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..641042c
--- /dev/null
@@ -0,0 +1,120 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+
+       <http pattern="/css/**" security="none" />
+       <http pattern="/js/**" security="none" />
+       <http pattern="/images/**" security="none" />
+       <http pattern="/favicon.ico" security="none" />
+       <http pattern="/one/css/**" security="none" />
+       <http pattern="/one/js/**" security="none" />
+       <http pattern="/one/images/**" security="none" />
+
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="loginUrlAuthenticationEntryPoint">
+               <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+               <intercept-url pattern="/logout*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+
+
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
+               <custom-filter position="LOGOUT_FILTER" ref="logoutFilter" />
+               <custom-filter position="LAST" ref="controllerFilter" />
+               <remember-me services-ref="rememberMeServices" key="SDN" />
+       </http>
+       
+       <beans:bean id="controllerFilter"
+               class="org.opendaylight.controller.web.ControllerCustomFilter" />
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="authenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="authenticationFilter"
+               class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+               <beans:property name="authenticationFailureHandler"
+                       ref="authenticationFailureHandler" />
+               <beans:property name="authenticationSuccessHandler">
+                       <beans:bean
+                               class="org.opendaylight.controller.web.ControllerAuthenticationSuccessHandler">
+                               <beans:property name="targetUrlParameter" value="x-page-url" />
+                               <beans:property name="defaultTargetUrl" value="/" />
+                       </beans:bean>
+               </beans:property>
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.web.ControllerWebSecurityContextRepository" />
+
+       <beans:bean id="authenticationFailureHandler"
+               class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
+               <beans:property name="useForward" value="false" />
+               <beans:property name="defaultFailureUrl" value="/login" />              
+       </beans:bean>
+
+       <beans:bean id="loginUrlAuthenticationEntryPoint"
+               class="org.opendaylight.controller.web.ControllerLoginUrlAuthEntryPoint">
+               <beans:property name="loginFormUrl" value="/login" />
+       </beans:bean>
+
+       <beans:bean id="authenticationProviderWrapper"
+               class="org.opendaylight.controller.web.AuthenticationProviderWrapper" />
+
+    <!-- logout related -->
+    
+    <beans:bean id="logoutHandler"
+        class="org.opendaylight.controller.web.ControllerLogoutHandler" />
+        
+    <beans:bean id="securityContextLogoutHandler"
+        class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />    
+        
+            
+    <beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
+        <!-- if logout succeed then this is the URL -->
+        <beans:constructor-arg value="/login" />
+        <beans:constructor-arg>
+            <beans:list>
+                <beans:ref bean="logoutHandler"/>
+                <beans:ref bean="rememberMeServices"/>
+                <beans:ref bean="securityContextLogoutHandler"/>
+            </beans:list>
+        </beans:constructor-arg>
+        <beans:property name="filterProcessesUrl" value="/logout" />
+    </beans:bean>       
+        
+
+
+
+       <!-- remember me related -->
+       <beans:bean id="rememberMeFilter"
+               class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+       </beans:bean>
+
+       <beans:bean id="rememberMeServices"
+               class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
+               <beans:property name="userDetailsService" ref="userDetailsServiceRef" />
+               <beans:property name="key" value="SDN" />
+               <beans:property name="alwaysRemember" value="true"></beans:property>
+               <beans:property name="tokenValiditySeconds" value="3600" />
+               <beans:property name="cookieName" value="SDN-Controller" />
+       </beans:bean>
+
+       <beans:bean id="userDetailsServiceRef" class="org.opendaylight.controller.web.ControllerUserDetailsService" />
+
+
+       <beans:bean id="rememberMeAuthenticationProvider"
+               class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
+               <beans:property name="key" value="SDN" />
+       </beans:bean>
+       
+</beans:beans>
diff --git a/opendaylight/web/root/src/main/resources/WEB-INF/web.xml b/opendaylight/web/root/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..6c8a560
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+       version="2.4">
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <listener>
+               <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+       <servlet>
+               <servlet-name>RootGUI</servlet-name>
+               <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+               <load-on-startup>1</load-on-startup>
+       </servlet>
+
+       <servlet-mapping>
+               <servlet-name>RootGUI</servlet-name>
+               <url-pattern>/</url-pattern>
+       </servlet-mapping>
+
+       <filter>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <display-name>Cisco ONE Controller</display-name>
+       <description>Cisco ONE Controller</description>
+
+       <listener>
+               <listener-class>org.opendaylight.controller.web.ControllerUISessionManager</listener-class>
+       </listener>
+
+</web-app>
diff --git a/opendaylight/web/root/src/main/resources/css/bootstrap.min.css b/opendaylight/web/root/src/main/resources/css/bootstrap.min.css
new file mode 100644 (file)
index 0000000..fd5ed73
--- /dev/null
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v2.3.0
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}
diff --git a/opendaylight/web/root/src/main/resources/css/login.less b/opendaylight/web/root/src/main/resources/css/login.less
new file mode 100644 (file)
index 0000000..cdaba23
--- /dev/null
@@ -0,0 +1,53 @@
+.reset {
+       padding: 0;
+       margin: 0;
+}
+
+html , body {
+       .reset;
+       height: 100%;
+       width: 100%;
+       background-color: #eee;
+}
+
+html {
+       display: table;
+}
+
+body {
+       display: table-cell;
+       vertical-align: middle;
+       padding-bottom: 10%;
+}
+
+.container {
+       width: 650px;
+       .content {
+               padding: 25px;
+               background-color: #fff;
+               -webkit-border-radius: 10px;
+               -moz-border-radius: 10px;
+               border-radius: 10px;
+               -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+               -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+               box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+               text-align: center;
+               .login-form {
+                       #logo {
+                               background-repeat: no-repeat;
+                               background-position: center;
+                               background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAADQCAMAAADGQmMsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBQ0I5NEY2RjhBM0QxMUUyOUMzMUNFMzQ4NjM5NUNFRiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBQ0I5NEY3MDhBM0QxMUUyOUMzMUNFMzQ4NjM5NUNFRiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFDQjk0RjZEOEEzRDExRTI5QzMxQ0UzNDg2Mzk1Q0VGIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkFDQjk0RjZFOEEzRDExRTI5QzMxQ0UzNDg2Mzk1Q0VGIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EBgRdAAAAwBQTFRFrKus/sxW8VIj////Y2Nk/s9gysrL+qMY/tl//uWp+6wW9ogeW1pco6Ok8E4jzc3N/b8p/chJ//jp/tZ2/tyJ/cZC+Pj48vLy//Xd8lwi7Ozs820hbGts/bscxcXF4eDh//LV/tFke3t8U1JU//DN/b0l6enp9fX1/s5d/+3E3t3etrW2u7q7/uzA/cAt/t2P/uq5/LIV/tRx//bi/uOh/bUU/cExTEtN5ubm/clM9oQfwcHC//fl/ui09Hkh/tmA/cI0/cQ8/bgU/+/K2tra/td61dXW/tNs8Vgj//vzmpqb/uy//uGd8mUi/cM5+qUX9YUg/bkWdXR2/tqE0tLS8Ewj//z2/uSk/uiy/tJo9HEh/r8S8mAi9Hgh//789HYi/t6T9X4g//34hYSF/bwh//bg/uCY+6wa//HQcXBx/bwi/Pz88mIi//vw//z0vb29//Ta/r8R/stS9YIf/boa9HkilpaX//PX/sER/bcT/+7HkZGS/s1Y/cdF+vr6//rviYmKnZ2e9vb2/cU99YIg2NjY+JQbgYCCjYyN/um28Eoj/uSm//76V1ZYsbGx9Xsf/t6Q/uu88WAi/t+V/tuI+Zwa/uas/boT/tBi/bgT/cQ694sdRkVH9HMh/uCW/bwV9oIg828h+Zgb/tl59Xsh/rgT9YAgQkFD/r0S/t2N9X0h/ueu/uevj46Q/bwfx8fI//PY/rwS/boZ+Z4Z9Hwhb25wi4uMxMPE/tyM//LT//ns/chH//rt//jn///+//HS/bkT/b0SR0ZI9YEg/bkYQ0JE/uau/v7+/s5b/b4RSUhKh4aIRENFv7/AdHN0/s1anJuc/b4S9H0hd3Z3/LAV7+/wUE9R/stU/tRv09PUeHd59Hwg8WIiXl1f/uewqKeos7O0/bkSt7e3/r0R4+Pjc3J029vc8mMiYWBhmJeZiIeIl5eY/rsS+Z0ZhIOFTk1P9Xwg9X0goJ+g9oEgZ2Zo/b0R0M/Q/r0UcG9x/tuGvLu89X4h/rwTlJOUg4OE9H8hK2JASQAAFM5JREFUeNrsnXtAVVW+x8WlJop6BEFASRRQaRQNFIRB0oMIiiDkIx+ASmioaIoPEjVrtGt1oxS91kq9k43DYsZHaZniA80081VZaJMzjl6vDU5Ttxmv13vNW87a57kfa629z0F8nPP7/gV7r/04+7N/6/Fbv9/aTRDI69QEHgFABwF0EEAHAXQQQAcBdBBABwF0EEAHAXQQQAcBdBBABwF0EEAHAXQQQAfoIIAOAugggA4C6CCADgLoIIAOAugggA4C6CCADgLoIIAOAugggA4C6AAdBNBBAB0E0EEAHQTQQQAdBNBBAB0E0EEAHQTQQQAdBNBBAB10D6DPWffxFpbWTYXn7rHQ1+Ry9AQ8d4+F3gdz1A+eu8dCn8yDDpYO0EEA3ZhCb1x88fHOu3Z1nv5BkxahrBIzv27XQq3RZ1WFzrbTFrJpiKNQ9478+5g5ul0PgH5XoO9+MoHIdOLn09oyHX8gWiV0u3RMXug04crPUejVZs9y7yToBBnqzdC3FrsHPXWgq8inUyadF19ud6V79wHtei3uTP+9rsHew4+sXLxNrsUrO58i5Oo7skIPk4htykJ2PddEVugkH7ofGeLF0H2xaY470IuL8DhXrjNgJSHn2jwi3/RCkwhCzg9QQa8g2mp/wKULhAyXQ39c/4rvENIEoLOZYxwf5zr0sii6xQVbH1JBDn9+VlOXX0wgFe3U0B9hHN/jmpz6w2S6IeikHUBnM6fU57gKvdgkbck1TH3oOdLsL6wdXT4lh28YgI7Qj4S8esw16IdJxIBagM5krrR1I9DDo2zbDFLvkkC6cXrLx06SDaONQEdtSEKoa9BfHE6uBwF0NnOFrRuAftBk32bM1n91lfwfvyf9LYk4awQ6uk7+5hr0hagZWQnQOcwp9anGoTvsXNKfDVzoA+InGDNTmN0MQd9NEjq6BP05dCWBtAHoHOYYDwo2Cj08Rr419yXdC71AyA3R/isnyC+NQEcXyNMuQe8mlSSnATqHObX1YGPQa6JU27fqXWk6+VZc4CK5MNMI9IXkJ5eg75IeFTncBaBzmDtsXQe60s6N2PoQcrS7uMSzEU5TF0HvZR+euwAdnSQXOgJ0SbVa5tgcE6wPvWYG48Atwmu9SLbp3c41Z39LBP0GaeY69JmdyZcAnW3nFlufqge9LIa1J7ev4FJBEfrPd8CGw6EGoH/tDnTUPULuzPNa6LVs5tTW54ihHyxi78oV2PoN4jdT94522btod97SERpNyCWAvo7HFQ+KE0Fn27lFfFtvoteNszpefjIA/bQ7bTrVJXLqL94O/QnMV4w/943oHVfEP878Me9qJ8mr+rc0mnQ2AH24G713ixaTilDvhr4OixQY+7/sHTt+s1x4IMfWaT/qhv49XSERM3WhH/MjvdyDjr4knWd6M/QnhOjw6yNbnWFtn9hq5OvCAzm2Th/vI/o3dfaE3a8ugN6CJPSwQ3/cNej0LrZ5MfSBYua4+u0Vjx7Xbp5bd/v7ap1De7Ou1yPiVEf9u6IQXtCFfv3oQmSHXjH8+eEM/XU3Gzp6YYOilfEy6GWDdaC/Nynp0bkaO697a9JDOtADg1nXCyUJMw1BH6oH/Zp8lo2jo9040NFuQlp4b/Uel60D/fcLctTU59bN27xZB3rgXublOp7bYMTSL+ha+mXitNWHyYVeT/di6BdDedDR5yQi1Hs7cvsDdaBXpaioH6+bl1KlA739MK4NG3i8HX843F0IfebzhHyOnNBdbNMl/SybXPe+IZvQ1iXoKuqSnVfpQOfYOaV1nRuzJHfJkYggex/gqLZmuHLpqpy56713C+ir5Gcvds7sDdSBXpWS9OhEZ7+d2rkOdJ6dUz1JDNzt10ev2zt+fuT8k0p1a0Zb6wvKaFg3oKMB5xyT697oe0/O1oEu2fpxuZ2LoXPtHEnzpk8a+EHkOTv0q4z+2blPf6EIe3IPOmpx1D657pUTLnxbt0GXbP2M1c7fSqnSgb5vluBSRnzvx7rZ3S6Spbe5rFCvFkPVFb6b0Om7dcI6uR5U4Y3z6QeydaBL1I9Ldr5ic5UO9KX7hX20c+RrXd9MgmPKnbbp+glH7kJH35Jmlirj2WZeGUThH6gDndbwdRMddi6ALrRzS6N+Uu9mLjp74yLfe8OhH5tuu1I37wyX8n9TB/pHKUmH+r9ls3M+9KXLdC7Ujhy9olPkqqN2b2ToKDTiqGUUcN5LAyMPBIqhV31U9dprVVU60Pct073QN3pduUukIuguQafjBMsL5q3Q0d7BYug5+RkZKTli6Hp1u6ShRJwiejbCmbvS6NBRL8vdeC10dGC5CHpOfkheZWV+kgi6uA9n138RP1Hn7Lx83rPRoaOFpOIs+tJ705r2LudDp8xL6rPyHNRZ0NvPNnQZasqf8vf+lWyQBSk3PnTaiTt/7GcvTmAcW3poh1JzH7q9QlJOfl5JFlVlZUqOZcOtkc+oih7yqTV4maGHyXNBXObkMrqr0Hv4kZ9Wegf0sj1rO2g0ImDk2/+m0HutO0na1KlphlVNM+g/klq/97284PdvP5U2TXvGtR1YrfzDp8g3zNVGfrWNKBOP7gJ0NOQwObzBK6CzI2C/q8uZ9L5ctzrlUWWFtH25rU0vt62sl7Z1uqUoOKn1o98xT8lcZLDFCVLxI2NzM/XKAXcDujQff9groG85wkT0P/1bL6iSacWmvHralLcd09ahMW3zsurr8yaskJdbsH7RDrazJo5tXdMJ+eaXCn9q0I1uhPip5uAMQv+yQdDRNXLKO6DvZDM60791igq6krkkSl0JPWX9ojOcWLk4zg20+YGQiL/92MXSke/R5fQ1aWploXp1CoPQr+5+eDdTTw8xAt1beu886Pi4gjqFrmUu2boCesp6zPPEc6Gj0IsXLFNmV69fbxYh/VXxeRdNV7AjMYDjaf7qUs5xwmnZSlMaBe16x6uhK219xaZ6DXOLrdc7od/m27kIutSGD+/mZ2VT8fhPLVizb0HPLw7V/S2PLF78AUcvXnYWEj2mAQO8G7pk6452fcWEEBbzMW0rNzqg5/PtXAe6RLV7lytXugwIQqB7Cl2ydTv1nE4s5hL1TTaP7O0FAjvXhw66T6Dj44ts1HPyM5jM6cgtIyXJ1m/Homg5gP6gQJdsPV9iPikvoy1HGZWW2Rdq5x9igO4J0K19+JxJIVk86GNCSiQ/fP76Q+L4d4D+4EDH1f1b384PKRFAz8qrTLm1fpFOogtAf4Cg4+PVUzLy6kXQs/Iy5s2tXgTQPQf63P5TQiqzxNBD5i06jgG6x0Cf2Gre7b9X5omgV/5pyq2kuokA3VOgW3IaklIq80K40Cv/9H6SLTIaoHsCdFusc85r9U25Q7b6fyRZ4uHrzgB0T4A+t+4ja6xz0h8zXuY4Z0KmJMmyIAD6gw59rjOngeuGbTohx5kFAW7YBx76RLudWyZcKpuyJlya5jkmXD4S2vqDD70wOTg8OjI4ucCToctylyxTqyVa6mOa1pfI5tOljCeD0Av7BCj1O99iI09zTUDATWW5ZfTgVcIDYh0B2b4BAeIVa/sFBGQydxRPTg+Mmk9/R5hp3x9GaLNxD6QFtPTnnXVEQMBq619lAUKtKb7H0CfWOXOXrEEUGuqUuSKIQmTrKuhjtYvPhcV3HThM527LpILKdSgLS0WLFEoLKC0Za//PB+N04fnbY9xSu3VZv31hils1NY9WvxR0aw3npLWBjrNuEY9rY/rdW+jUzjerw6VU1CXm6nAprq2robNXn3tzTaHwbgMsqRTKbWMHUQqcTPhZJvokHcxROpOpTM0x7qM1/0HWm9u+vLTUJ9D2tn5SrigTHoa3v8s76xKMf2f96yUx9OXr7g70j3M5dj5vszYwUkF9TNMSRmCkLX9dLzBSgj5+Wh+7Jo9qHmj9GEi26Hf7z8e59H5VZlaOuQYsvSSyCt0d6ME+0m0V+UwLj5tdW4vGJkdOK5Xs/sjaAteh+48b6FTfdTH0Iax2bvhzdPLdgc4Ogf7/VlMm/V6u25YQ6PpKeQh0iTUE+rai4KT/brXDQAi0BF21wFyy73jLC5jGb9sfwzh2FLUy1eY/8Fary6Q7YlGDoGdK72LRKOVH6sLTJOyJyS5DV2kwxtPuRUcuedzHWzWKbNnqvZEKtVq/aePGjRMmTQjJCLEoI4T+Szd1+pdWypIPPdWhXHvGLVvHaqBrv+5Tlm6mD7M5717HUsuIPEBLhCu376XbBzHahYJsWrnPahB0S32crh15BNNT4ai4hkEfls17GxoZOvtNGPx6tVLP2NOa/lhfaUlryppiTWv6YuQzqqKvG0lfZENHKFpqP3u+wulbYxyIUE+MA1Q7+tKDHtOWH6Ws3N2AHi31ePox07QGRuHsAx4EvXg7P4Ex6bXKvKysvJJ/rOAnMMbMcRs62luqqpJlhrscm2n3Nhqbw9Sjo670oEh1+VRabXRFDYHuL9XtvE5Gefp+5DnQi02iVOWklMrKypK/C1OV44Pdho6G0WET+3ut1J5Nw6wjoLXqHl6RpRJQviO0XNH+BkHvKWCO3Om937fQI8PEixIkfZERMmWFcFECs6nMbehobzw2x7AWskjEeJStlo9RJ0NvpVfdo9y0R0vMRejSIH8U8gboB006y4/cvtU65wu95Uf0bF0AHfWl1fJNxstIh0mW/nJhjFn71fZPMN6pGCC8ewTjN1BDoBcsN+PsAm+AzrRzBfT89dXfObMgeAsNmcLdho7eoB1jrbcl3dHWd6B1uRrGgRkYf6WqF4oONAh6X+H3SDwIOtvOZdClnIYPZVkQ3CXF4qe6DX0OHa9rHJJTd2Kz7UU6QF/M1RoHN73mCNW/tQ2C3lXbT/BI6Bw7l0FfsP4/KOX/tGdBCBYPFNq6ELr07BMZzrVEmTemVOPhpt3+MIe7JG4+xp+xKgvj0JeZGK+eB0KvMektE2rPabBlQQiXCY2Pcxf6OIxzVR7JYVGyA4rpfs2EVHCYzK/zGX0D4hoGvZz2EpI9H3rqdr0FgfMtdi6J2nqK3oLApho3oSdv13S8p2E8WNFtG8/y3WBfp3N5BGoY9FW0aqn1eOh8O7dDz5flNFRb23Xh0t98WxdDl6Y4lWOlgkEKin2ZhryUvmcWb+9eE6N9cBW6VNjTodemFukt8q/MXbKuWiBe5J9r6zrQx6s98L6KKVJUMBibtX7Xsp02FxxllxvcUOiJFv+fZ0PXfAtbAz1flbtksXWdz3nExLkFfa26p0aNOE3VV48aqzmsj3W6TZol6YMaCn2QUW+cDXrRMMEQ9P6EXl6k8+Ge9/PX/7ZandO6foHeh3s4tq4DnY7E28uq1tpojOcrTHesiTkrSc8ajxCncncZ+hHVxP3Urs2V2iOHnvtVc45M9yn06DDdT3QxcpE/7N96o94nuqIOumfpCl/YG5qO2yiMBxWwvHZ4z01u6JKL0MNUzuRUTaSLHLpQ9yX03rnim97xduvfVrPy13P0PsYXFukG9D1KWw3WTqJJDpotzLdF0lrUcOjxmm+BF8sfkjTz7/NgQ0eZ4rs+9FR/JtwP61odEh5YlOpO9Z5mVkAPwHifpq9nZtXhBZbl6gML7gD0NzVt+uzyaKeK96igh/lGRrOUmrrvvu3ICTvvuOfNX7N3fLdqibBJn+NWR+4zbJbNhPuHMSZbI82aYDlbO2UO40USuwR9iU4sU28V9CKuJ6fn/TtO7z1f8K0t9K+8XasLBJ9VnpHq1pBNOqWs+72K2X4nsgOrfATxVi5BjzVzgjlsGqiC/mA6Z1Jn8NglDkOTefHs/dDepa7auR708Fz5jIoUGsewOTowMzPO35XlrHMHej9+M+FB0FE0x9YTZyMudGlmez/H1qMi3XTDUtOOcgY00komG7FtOo3JresdgV5Df0GZ50OvTWU6aBKl5y+CzrF1kyBUTgi9YJC8Sa+lRbMfS9OoZXv6avg3HvTCbHHcjIdAt0V/qplbPF9C6GjZcrMrdq4D3VcRxbpa1FHs0HjQUUuMYwq9ADoq19h6e2s9K4bO+J6bOExOBL0wHuP4QnktLnDzjm086O/mcry5ngZdY+uJtqevAx3NCnTBzsXQWyrmRaXUwNg1exia3BIzHDR3DrrkB5wR5w3QVbbe3h6WqgddZes6IXIi6OuUOYrjFRPpmgFwYCNCD56PzYncsi95EHRUvlNr5wagK2w9Si/Rmg9dCmY2O1+Z4O0CH4mUq7a68aBboqi5Y/VoT4KOymdo7NwIdOTvGLmZDiJ3oT8h+bdl89iPYbOJ+2U/Ke/BpxGh1/LTbdCwN+S+4Qc/ral8vtrODUFHw2y2HlWD3ISeHCsd/+/ODbNMZlYMvGwInxveeNCtX6grZcX1li9X5M95QAKjdXK9vTwnyAh0lGyxdZOBRTQodLMmpjx4j6U/MVlmav0w3imIr3wlRmOJOtBHuQQdJUsJlVFr1J65uJZShSR7gTwAOkqdr7Rzg9Attm6qQYagyxNKC5cl10wutSCfIe+P18YLnKo23912/wZbegEXOprV07JUQtpBZxuzrDxdiibcKQ+m8gToKDp3sDKdzBh0lBwfFokMQg9s7mPTkqWD7DO76Qo33jjaqSsXnUeagLvpCvT4z3y0Kk17hQcdoT7WW8uOXeO7enWm7+R062ok7VORp0FHkSoHp0HoKLkGGYXOcNYHqFqGRF7gk0MBtGqZ5QJ0tuxLGjDXnAmO1c46Z6tiJj0DulpGoRvUbE24dVRiej+1J13KJhsoPlGZOiwlkZH9Ytcn3FjtQsfBrEY/eG3pEXlObldfdYli7foYTi1lLplAm44ozg4PhV6QmqlQ74Os7low3aNbJWVmKqqH8MxM7tMvy+QossBxMCcF792X1sYuSSxN/Cp2VSZjwbjZvTOjud+SPsg5q/QQpnoRdNCdEkAH6HdUfXjQ+8Fz91jo04o48oXn7rHQQQAdBNBBAB0E0EEAHQTQQQAdBNBBAB0E0EEAHaCDADoIoIMAOgiggwA6CKCDADoIoIMAOgiggwA6CKCDADoIoIMAOgigA3QQQAcBdBBABwF0EEAHAXQQQAcBdBBABwF0EEAHAXQQQAcBdBBABwF0gA4C6CCADgLoIIAOAuig+13/FGAApyIqjoQlf2EAAAAASUVORK5CYII=');
+                               width: 400px;
+                               height: 175px;
+                               margin: 0 auto 20px;
+                       }
+                       .icon-login {
+                               width: 20px;
+                               height: 16px;
+                               background-position: left center;
+                               background-repeat: no-repeat;
+                               background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAc5JREFUeNpi3NLKAAKMQBwNxP5AbM0AAUeBeCMQLwXi/95V/xmwARao5nphGeN6KTUPBlF5S7DE64fHQ57d2hHy9slZFSC3EWQILgOiQZqVjRMY3r+4zHBqYx5YQlLVhQEkBjIcaMgdIL0EmwFMIGeDbAZpvn1yVtGXd/cEQRjEBomB5KBeY8BlgDXI2c9v7wHx53vnnf0AwiA2SAzqJWt8BlAEQAYcBQYY2M9AkLh1krEACIPYIDGQHDRGcBqwERjaDIISugyq5ml9PEJK70EYxAaJgeTYWTRDcBnACEwHuKIRrPnLi28MipZBDF+f7WJQcpoAUsvAI2qFYgDOhASy+cXDzwx6dpIgzQz39hWADQHRIKAXfoqB8f///zgD6NJKMxD1/9Kh5yiGQC0EG0AwFm6f/sAI0gwyBOoCBuRUSVQ0ggyRkOfFaggLIc2qpgIM7558/K/jW8Lw7PwMsCEMDAUMYloxDIcm6U7FGwYwcKBX/b+0liqDoHIQw4UNPQyggJVTF2Cwy7vMyOgizMj4W4iDkZGZhYmdmZGBi/E/Cws7IzMDCwvLPyZGlvdf/nK+//qHuy9L6gbIsNTex/oT8xWKvCuug3Ldd4AAAwBj/b+B1P2YnAAAAABJRU5ErkJggg==');
+                       }
+               }
+       }
+}
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/css/one.less b/opendaylight/web/root/src/main/resources/css/one.less
new file mode 100644 (file)
index 0000000..cb6fbc5
--- /dev/null
@@ -0,0 +1,260 @@
+// properties
+@mainTop: 43px;
+@mainBottom: 41px;
+
+@minWidth: 640px;
+@minHeight: 480px;
+
+@topologyOffset: -4px; // ensure calibration
+
+// mixins
+.dash-size (@width, @height) {
+       width: @width;
+       height: @height;
+}
+.position (@type, @top, @right, @bottom, @left) {
+       position: @type;
+       top: @top;
+       right: @right;
+       bottom: @bottom;
+       left: @left;
+}
+.dash-position (@top, @right, @bottom, @left) {
+       .position(absolute, @top, @right, @bottom, @left);
+}
+.icon {
+       width: 22px;
+       height: 16px;
+       background-position: left center;
+       background-repeat: no-repeat;
+}
+.dashlet-elements {
+       width: 98%;
+       margin: 5px auto;
+}
+
+// header
+#menu {
+       .navbar-inner {
+               .brand {
+                       font-size: 1.4em;
+                       font-variant: small-caps;
+                       margin: 0 12px;
+                       padding: 10px 0 10px 45px;
+                       background-image: url('../img/logo.png');
+                       background-position: left center;
+                       background-repeat: no-repeat;
+               }
+               ul {
+                       margin-top: 10px;
+                       border-bottom: transparent;
+                       li {
+                               a {
+                                       padding: 5px 15px;
+                                       &:hover {
+                                               border-color: transparent;
+                                       }
+                               }
+                       }
+                       .active {
+                               a {
+                                       border-radius: 1px;
+                                       background: #f6f6f6;
+                                       &:hover {
+                                               border-top: 1px solid #ddd;
+                                               border-right: 1px solid #ddd;
+                                               border-left: 1px solid #ddd;
+                                       }
+                               }
+                       }
+               }
+               #toolbar {
+                       position: absolute;
+                       right: 10px;
+                       .dropdown-menu {
+                               right: 0;
+                               left: auto;
+                               min-width: 120px;
+                       }
+                       .icon-user {
+                               .icon;
+                               background-image: url('../img/user_0020_16.png');
+                       }
+                       .icon-users {
+                               .icon;
+                               background-image: url('../img/user_group_0107_16.png');
+                       }
+                       .icon-save {
+                               .icon;
+                               background-image: url('../img/save_as_0106_16.png');
+                       }
+                       .icon-logout {
+                               .icon;
+                               background-image: url('../img/open_1054_16.png');
+                       }
+               }
+       }
+}
+
+// footer
+#footer {
+       #alert {
+               margin: 0;
+               p {
+                       padding: 0;
+                       margin: 0;
+               }
+       }
+}
+
+// main
+#main {
+       position: fixed;
+       top: @mainTop;
+       right: 0;
+       bottom: @mainBottom;
+       left: 0;
+       min-width: @minWidth;
+       min-height: @minHeight;
+
+       // topology
+       #topology {
+               position: absolute;
+               top: @topologyOffset;
+               right: 0;
+               bottom: @topologyOffset;
+               left: 0;
+               background: #ddd;
+       }
+
+       // left
+       #left {
+               .dash-size(30%, 100%);
+               float: left;
+
+               #left-top {
+                       .dash-size(100%, 60%);
+
+                       .dash { 
+                               .dash-position(5px, 5px, 5px, 5px);
+                       }
+               }
+
+               #left-bottom {
+                       .dash-size(100%, 40%);
+
+                       .dash {
+                               .dash-position(0, 5px, 5px, 5px);
+                       }
+               }
+       }
+
+       // right
+       #right {
+               .dash-size(70%, 100%);
+               float: right;
+
+               #right-top {
+                       .dash-size(100%, 60%);
+
+                       .dash {
+                               .dash-position(5px, 5px, 5px, 0);
+                       }
+               }
+
+               #right-bottom {
+                       .dash-size(100%, 40%);
+
+                       .dash {
+                               .dash-position(0, 5px, 5px, 0);
+                       }
+               }
+       }
+
+       #left-top, #left-bottom,
+       #right-top, #right-bottom {
+               position: relative;
+       }
+
+       // dashentries
+       .dash {
+               border-radius: 1px;
+               background: #fff;
+               border: 1px solid #ddd;
+
+               .nav {
+                       padding: 2px;
+                       padding-bottom: 0;
+                       margin: 0;
+               }
+
+               .nav-tabs {
+                       background: #eee;
+                       li {
+                               a {
+                                       background: #ddd;
+                                       padding: 4px 8px;
+                                       margin-right: 3px;
+                                       border-radius: 1px;
+                                       &:hover {
+                                               border-color: transparent;
+                                       }
+                               }
+                       }
+                       .active {
+                               a {
+                                       background: #fff;
+                                       border-radius: 1px;
+                                       &:hover {
+                                               border-top: 1px solid #ddd;
+                                               border-left: 1px solid #ddd;
+                                               border-right: 1px solid #ddd;
+                                       }
+                               }
+                       }
+               }
+
+               .dashlet {
+                       overflow: auto;
+                       .dash-position(45px,0,0,0);
+
+                       h4 {
+                               .dashlet-elements;
+                       }
+
+                       p {
+                               .dashlet-elements;
+                       }
+
+                       table {
+                               .dashlet-elements;
+                       }
+                       
+                       .btn-group {
+                               margin: 5px;
+                       }
+                       
+                       .none {
+                               margin: 0 auto;
+                               padding-top: 128px;
+                               background-position: center;
+                               background-repeat: no-repeat;
+                               background-image: url('../img/alert_unreachable_2008_128.png');
+                               text-align: center;
+                               p {
+                                       cursor: default;
+                               }
+                       }
+               }
+       }
+
+       // defaults for visual topology
+       #right-top .dash {
+               background: #ddd;
+               overflow: hidden;
+       }
+}
+
+.table-cursor tr:hover {
+       cursor: pointer;
+}
diff --git a/opendaylight/web/root/src/main/resources/img/Device_pc_3045_default_64.png b/opendaylight/web/root/src/main/resources/img/Device_pc_3045_default_64.png
new file mode 100644 (file)
index 0000000..cd4fff6
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/Device_pc_3045_default_64.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/Device_switch_3062_unknown_64.png b/opendaylight/web/root/src/main/resources/img/Device_switch_3062_unknown_64.png
new file mode 100644 (file)
index 0000000..c55127d
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/Device_switch_3062_unknown_64.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/Expand16T.png b/opendaylight/web/root/src/main/resources/img/Expand16T.png
new file mode 100644 (file)
index 0000000..489a4d6
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/Expand16T.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/Key_0024_16.png b/opendaylight/web/root/src/main/resources/img/Key_0024_16.png
new file mode 100644 (file)
index 0000000..1faeb82
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/Key_0024_16.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/alert_unreachable_2008_128.png b/opendaylight/web/root/src/main/resources/img/alert_unreachable_2008_128.png
new file mode 100644 (file)
index 0000000..cb0e446
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/alert_unreachable_2008_128.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/logo.png b/opendaylight/web/root/src/main/resources/img/logo.png
new file mode 100644 (file)
index 0000000..46c02e2
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/logo.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/open_1054_16.png b/opendaylight/web/root/src/main/resources/img/open_1054_16.png
new file mode 100644 (file)
index 0000000..62ee475
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/open_1054_16.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/open_1054_24.png b/opendaylight/web/root/src/main/resources/img/open_1054_24.png
new file mode 100644 (file)
index 0000000..055cc3c
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/open_1054_24.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/save_as_0106_16.png b/opendaylight/web/root/src/main/resources/img/save_as_0106_16.png
new file mode 100644 (file)
index 0000000..9316622
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/save_as_0106_16.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/save_as_0106_24.png b/opendaylight/web/root/src/main/resources/img/save_as_0106_24.png
new file mode 100644 (file)
index 0000000..1afce22
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/save_as_0106_24.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/topology_view_1033_128.png b/opendaylight/web/root/src/main/resources/img/topology_view_1033_128.png
new file mode 100644 (file)
index 0000000..de0d641
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/topology_view_1033_128.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/user_0020_16.png b/opendaylight/web/root/src/main/resources/img/user_0020_16.png
new file mode 100644 (file)
index 0000000..1ed7a09
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/user_0020_16.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/user_0020_24.png b/opendaylight/web/root/src/main/resources/img/user_0020_24.png
new file mode 100644 (file)
index 0000000..b8c3715
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/user_0020_24.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/user_group_0107_16.png b/opendaylight/web/root/src/main/resources/img/user_group_0107_16.png
new file mode 100644 (file)
index 0000000..a0e017b
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/user_group_0107_16.png differ
diff --git a/opendaylight/web/root/src/main/resources/img/user_group_0107_24.png b/opendaylight/web/root/src/main/resources/img/user_group_0107_24.png
new file mode 100644 (file)
index 0000000..427a871
Binary files /dev/null and b/opendaylight/web/root/src/main/resources/img/user_group_0107_24.png differ
diff --git a/opendaylight/web/root/src/main/resources/js/bootstrap.min.js b/opendaylight/web/root/src/main/resources/js/bootstrap.min.js
new file mode 100644 (file)
index 0000000..95c5ac5
--- /dev/null
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f<s.length-1&&f++,~f||(f=0),s.eq(f).focus()}};var s=e.fn.dropdown;e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e.fn.dropdown.noConflict=function(){return e.fn.dropdown=s,this},e(document).on("click.dropdown.data-api",r).on("click.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("click.dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger("shown")}):t.$element.focus().trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(){var e=this;this.$element.hide(),this.backdrop(function(){e.removeBackdrop(),e.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/js/jit.js b/opendaylight/web/root/src/main/resources/js/jit.js
new file mode 100644 (file)
index 0000000..0f9ef03
--- /dev/null
@@ -0,0 +1,9843 @@
+/*
+Copyright (c) 2012 Sencha Inc. - Author: Nicolas Garcia Belmonte (http://philogb.github.com/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+ */
+ (function () { 
+
+/*
+  File: Core.js
+
+ */
+
+/*
+ Object: $jit
+
+ Defines the namespace for all library Classes and Objects.
+ This variable is the *only* global variable defined in the Toolkit.
+ There are also other interesting properties attached to this variable described below.
+ */
+this.$jit = function(w) {
+  w = w || window;
+  for(var k in $jit) {
+    if($jit[k].$extend) {
+      w[k] = $jit[k];
+    }
+  }
+};
+
+$jit.version = '2.0.1';
+/*
+  Object: $jit.id
+
+  Works just like *document.getElementById*
+
+  Example:
+  (start code js)
+  var element = $jit.id('elementId');
+  (end code)
+
+*/
+
+/*
+ Object: $jit.util
+
+ Contains utility functions.
+
+ Some of the utility functions and the Class system were based in the MooTools Framework
+ <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>.
+ MIT license <http://mootools.net/license.txt>.
+
+ These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
+ I'd suggest you to use the functions from those libraries instead of using these, since their functions
+ are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
+
+ */
+var $ = function(d) {
+  return document.getElementById(d);
+};
+
+$.empty = function() {
+};
+
+/*
+  Method: extend
+
+  Augment an object by appending another object's properties.
+
+  Parameters:
+
+  original - (object) The object to be extended.
+  extended - (object) An object which properties are going to be appended to the original object.
+
+  Example:
+  (start code js)
+  $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
+  (end code)
+*/
+$.extend = function(original, extended) {
+  for ( var key in (extended || {}))
+    original[key] = extended[key];
+  return original;
+};
+
+$.lambda = function(value) {
+  return (typeof value == 'function') ? value : function() {
+    return value;
+  };
+};
+
+$.time = Date.now || function() {
+  return +new Date;
+};
+
+/*
+  Method: splat
+
+  Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
+
+  Parameters:
+
+  obj - (mixed) The object to be wrapped in an array.
+
+  Example:
+  (start code js)
+  $jit.util.splat(3);   //[3]
+  $jit.util.splat([3]); //[3]
+  (end code)
+*/
+$.splat = function(obj) {
+  var type = $.type(obj);
+  return type ? ((type != 'array') ? [ obj ] : obj) : [];
+};
+
+$.type = function(elem) {
+  var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
+  if(type != 'object') return type;
+  if(elem && elem.$$family) return elem.$$family;
+  if(elem && elem.nodeType == 9) return 'htmldocument';
+  return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
+};
+$.type.s = Object.prototype.toString;
+
+/*
+  Method: each
+
+  Iterates through an iterable applying *f*.
+
+  Parameters:
+
+  iterable - (array) The original array.
+  fn - (function) The function to apply to the array elements.
+
+  Example:
+  (start code js)
+  $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
+  (end code)
+*/
+$.each = function(iterable, fn) {
+  var type = $.type(iterable);
+  if (type == 'object') {
+    for ( var key in iterable)
+      fn(iterable[key], key);
+  } else {
+    for ( var i = 0, l = iterable.length; i < l; i++)
+      fn(iterable[i], i);
+  }
+};
+
+$.indexOf = function(array, item) {
+  if(array.indexOf) return array.indexOf(item);
+  for(var i=0,l=array.length; i<l; i++) {
+    if(array[i] === item) return i;
+  }
+  return -1;
+};
+
+/*
+  Method: map
+
+  Maps or collects an array by applying *f*.
+
+  Parameters:
+
+  array - (array) The original array.
+  f - (function) The function to apply to the array elements.
+
+  Example:
+  (start code js)
+  $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
+  (end code)
+*/
+$.map = function(array, f) {
+  var ans = [];
+  $.each(array, function(elem, i) {
+    ans.push(f(elem, i));
+  });
+  return ans;
+};
+
+/*
+  Method: reduce
+
+  Iteratively applies the binary function *f* storing the result in an accumulator.
+
+  Parameters:
+
+  array - (array) The original array.
+  f - (function) The function to apply to the array elements.
+  opt - (optional|mixed) The starting value for the acumulator.
+
+  Example:
+  (start code js)
+  $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
+  (end code)
+*/
+$.reduce = function(array, f, opt) {
+  var l = array.length;
+  if(l==0) return opt;
+  var acum = arguments.length == 3? opt : array[--l];
+  while(l--) {
+    acum = f(acum, array[l]);
+  }
+  return acum;
+};
+
+/*
+  Method: merge
+
+  Merges n-objects and their sub-objects creating a new, fresh object.
+
+  Parameters:
+
+  An arbitrary number of objects.
+
+  Example:
+  (start code js)
+  $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
+  (end code)
+*/
+$.merge = function() {
+  var mix = {};
+  for ( var i = 0, l = arguments.length; i < l; i++) {
+    var object = arguments[i];
+    if ($.type(object) != 'object')
+      continue;
+    for ( var key in object) {
+      var op = object[key], mp = mix[key];
+      mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
+          .merge(mp, op) : $.unlink(op);
+    }
+  }
+  return mix;
+};
+
+$.unlink = function(object) {
+  var unlinked;
+  switch ($.type(object)) {
+  case 'object':
+    unlinked = {};
+    for ( var p in object)
+      unlinked[p] = $.unlink(object[p]);
+    break;
+  case 'array':
+    unlinked = [];
+    for ( var i = 0, l = object.length; i < l; i++)
+      unlinked[i] = $.unlink(object[i]);
+    break;
+  default:
+    return object;
+  }
+  return unlinked;
+};
+
+$.zip = function() {
+  if(arguments.length === 0) return [];
+  for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
+    for(var i=0, row=[]; i<l; i++) {
+      row.push(arguments[i][j]);
+    }
+    ans.push(row);
+  }
+  return ans;
+};
+
+/*
+  Method: rgbToHex
+
+  Converts an RGB array into a Hex string.
+
+  Parameters:
+
+  srcArray - (array) An array with R, G and B values
+
+  Example:
+  (start code js)
+  $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
+  (end code)
+*/
+$.rgbToHex = function(srcArray, array) {
+  if (srcArray.length < 3)
+    return null;
+  if (srcArray.length == 4 && srcArray[3] == 0 && !array)
+    return 'transparent';
+  var hex = [];
+  for ( var i = 0; i < 3; i++) {
+    var bit = (srcArray[i] - 0).toString(16);
+    hex.push(bit.length == 1 ? '0' + bit : bit);
+  }
+  return array ? hex : '#' + hex.join('');
+};
+
+/*
+  Method: hexToRgb
+
+  Converts an Hex color string into an RGB array.
+
+  Parameters:
+
+  hex - (string) A color hex string.
+
+  Example:
+  (start code js)
+  $jit.util.hexToRgb('#fff'); //[255, 255, 255]
+  (end code)
+*/
+$.hexToRgb = function(hex) {
+  if (hex.length != 7) {
+    hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+    hex.shift();
+    if (hex.length != 3)
+      return null;
+    var rgb = [];
+    for ( var i = 0; i < 3; i++) {
+      var value = hex[i];
+      if (value.length == 1)
+        value += value;
+      rgb.push(parseInt(value, 16));
+    }
+    return rgb;
+  } else {
+    hex = parseInt(hex.slice(1), 16);
+    return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
+  }
+};
+
+$.destroy = function(elem) {
+  $.clean(elem);
+  if (elem.parentNode)
+    elem.parentNode.removeChild(elem);
+  if (elem.clearAttributes)
+    elem.clearAttributes();
+};
+
+$.clean = function(elem) {
+  for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
+    $.destroy(ch[i]);
+  }
+};
+
+/*
+  Method: addEvent
+
+  Cross-browser add event listener.
+
+  Parameters:
+
+  obj - (obj) The Element to attach the listener to.
+  type - (string) The listener type. For example 'click', or 'mousemove'.
+  fn - (function) The callback function to be used when the event is fired.
+
+  Example:
+  (start code js)
+  $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
+  (end code)
+*/
+$.addEvent = function(obj, type, fn) {
+  if (obj.addEventListener)
+    obj.addEventListener(type, fn, false);
+  else
+    obj.attachEvent('on' + type, fn);
+};
+
+$.addEvents = function(obj, typeObj) {
+  for(var type in typeObj) {
+    $.addEvent(obj, type, typeObj[type]);
+  }
+};
+
+$.hasClass = function(obj, klass) {
+  return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
+};
+
+$.addClass = function(obj, klass) {
+  if (!$.hasClass(obj, klass))
+    obj.className = (obj.className + " " + klass);
+};
+
+$.removeClass = function(obj, klass) {
+  obj.className = obj.className.replace(new RegExp(
+      '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
+};
+
+$.getPos = function(elem) {
+  var offset = getOffsets(elem);
+  var scroll = getScrolls(elem);
+  return {
+    x: offset.x - scroll.x,
+    y: offset.y - scroll.y
+  };
+
+  function getOffsets(elem) {
+    var position = {
+      x: 0,
+      y: 0
+    };
+    while (elem && !isBody(elem)) {
+      position.x += elem.offsetLeft;
+      position.y += elem.offsetTop;
+      elem = elem.offsetParent;
+    }
+    return position;
+  }
+
+  function getScrolls(elem) {
+    var position = {
+      x: 0,
+      y: 0
+    };
+    while (elem && !isBody(elem)) {
+      position.x += elem.scrollLeft;
+      position.y += elem.scrollTop;
+      elem = elem.parentNode;
+    }
+    return position;
+  }
+
+  function isBody(element) {
+    return (/^(?:body|html)$/i).test(element.tagName);
+  }
+};
+
+$.event = {
+  get: function(e, win) {
+    win = win || window;
+    return e || win.event;
+  },
+  getWheel: function(e) {
+    return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
+  },
+  isRightClick: function(e) {
+    return (e.which == 3 || e.button == 2);
+  },
+  getPos: function(e, win) {
+    // get mouse position
+    win = win || window;
+    e = e || win.event;
+    var doc = win.document;
+    doc = doc.documentElement || doc.body;
+    //TODO(nico): make touch event handling better
+    if(e.touches && e.touches.length) {
+      e = e.touches[0];
+    }
+    var page = {
+      x: e.pageX || (e.clientX + doc.scrollLeft),
+      y: e.pageY || (e.clientY + doc.scrollTop)
+    };
+    return page;
+  },
+  stop: function(e) {
+    if (e.stopPropagation) e.stopPropagation();
+    e.cancelBubble = true;
+    if (e.preventDefault) e.preventDefault();
+    else e.returnValue = false;
+  }
+};
+
+$jit.util = $jit.id = $;
+
+var Class = function(properties) {
+  properties = properties || {};
+  var klass = function() {
+    for ( var key in this) {
+      if (typeof this[key] != 'function')
+        this[key] = $.unlink(this[key]);
+    }
+    this.constructor = klass;
+    if (Class.prototyping)
+      return this;
+    var instance = this.initialize ? this.initialize.apply(this, arguments)
+        : this;
+    //typize
+    this.$$family = 'class';
+    return instance;
+  };
+
+  for ( var mutator in Class.Mutators) {
+    if (!properties[mutator])
+      continue;
+    properties = Class.Mutators[mutator](properties, properties[mutator]);
+    delete properties[mutator];
+  }
+
+  $.extend(klass, this);
+  klass.constructor = Class;
+  klass.prototype = properties;
+  return klass;
+};
+
+Class.Mutators = {
+
+  Implements: function(self, klasses) {
+    $.each($.splat(klasses), function(klass) {
+      Class.prototyping = klass;
+      var instance = (typeof klass == 'function') ? new klass : klass;
+      for ( var prop in instance) {
+        if (!(prop in self)) {
+          self[prop] = instance[prop];
+        }
+      }
+      delete Class.prototyping;
+    });
+    return self;
+  }
+
+};
+
+$.extend(Class, {
+
+  inherit: function(object, properties) {
+    for ( var key in properties) {
+      var override = properties[key];
+      var previous = object[key];
+      var type = $.type(override);
+      if (previous && type == 'function') {
+        if (override != previous) {
+          Class.override(object, key, override);
+        }
+      } else if (type == 'object') {
+        object[key] = $.merge(previous, override);
+      } else {
+        object[key] = override;
+      }
+    }
+    return object;
+  },
+
+  override: function(object, name, method) {
+    var parent = Class.prototyping;
+    if (parent && object[name] != parent[name])
+      parent = null;
+    var override = function() {
+      var previous = this.parent;
+      this.parent = parent ? parent[name] : object[name];
+      var value = method.apply(this, arguments);
+      this.parent = previous;
+      return value;
+    };
+    object[name] = override;
+  }
+
+});
+
+Class.prototype.implement = function() {
+  var proto = this.prototype;
+  $.each(Array.prototype.slice.call(arguments || []), function(properties) {
+    Class.inherit(proto, properties);
+  });
+  return this;
+};
+
+$jit.Class = Class;
+
+/*
+  Object: $jit.json
+
+  Provides JSON utility functions.
+
+  Most of these functions are JSON-tree traversal and manipulation functions.
+*/
+$jit.json = {
+  /*
+     Method: prune
+
+     Clears all tree nodes having depth greater than maxLevel.
+
+     Parameters:
+
+        tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
+        maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
+
+  */
+  prune: function(tree, maxLevel) {
+    this.each(tree, function(elem, i) {
+      if (i == maxLevel && elem.children) {
+        delete elem.children;
+        elem.children = [];
+      }
+    });
+  },
+  /*
+     Method: getParent
+
+     Returns the parent node of the node having _id_ as id.
+
+     Parameters:
+
+        tree - (object) A JSON tree object. See also <Loader.loadJSON>.
+        id - (string) The _id_ of the child node whose parent will be returned.
+
+    Returns:
+
+        A tree JSON node if any, or false otherwise.
+
+  */
+  getParent: function(tree, id) {
+    if (tree.id == id)
+      return false;
+    var ch = tree.children;
+    if (ch && ch.length > 0) {
+      for ( var i = 0; i < ch.length; i++) {
+        if (ch[i].id == id)
+          return tree;
+        else {
+          var ans = this.getParent(ch[i], id);
+          if (ans)
+            return ans;
+        }
+      }
+    }
+    return false;
+  },
+  /*
+     Method: getSubtree
+
+     Returns the subtree that matches the given id.
+
+     Parameters:
+
+        tree - (object) A JSON tree object. See also <Loader.loadJSON>.
+        id - (string) A node *unique* identifier.
+
+     Returns:
+
+        A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
+
+  */
+  getSubtree: function(tree, id) {
+    if (tree.id == id)
+      return tree;
+    for ( var i = 0, ch = tree.children; ch && i < ch.length; i++) {
+      var t = this.getSubtree(ch[i], id);
+      if (t != null)
+        return t;
+    }
+    return null;
+  },
+  /*
+     Method: eachLevel
+
+      Iterates on tree nodes with relative depth less or equal than a specified level.
+
+     Parameters:
+
+        tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
+        initLevel - (number) An integer specifying the initial relative level. Usually zero.
+        toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
+        action - (function) A function that receives a node and an integer specifying the actual level of the node.
+
+    Example:
+   (start code js)
+     $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
+        alert(node.name + ' ' + depth);
+     });
+   (end code)
+  */
+  eachLevel: function(tree, initLevel, toLevel, action) {
+    if (initLevel <= toLevel) {
+      action(tree, initLevel);
+      if(!tree.children) return;
+      for ( var i = 0, ch = tree.children; i < ch.length; i++) {
+        this.eachLevel(ch[i], initLevel + 1, toLevel, action);
+      }
+    }
+  },
+  /*
+     Method: each
+
+      A JSON tree iterator.
+
+     Parameters:
+
+        tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
+        action - (function) A function that receives a node.
+
+    Example:
+    (start code js)
+      $jit.json.each(tree, function(node) {
+        alert(node.name);
+      });
+    (end code)
+
+  */
+  each: function(tree, action) {
+    this.eachLevel(tree, 0, Number.MAX_VALUE, action);
+  }
+};
+
+
+/*
+     An object containing multiple type of transformations. 
+*/
+
+$jit.Trans = {
+  $extend: true,
+  
+  linear: function(p){
+    return p;
+  }
+};
+
+var Trans = $jit.Trans;
+
+(function(){
+
+  var makeTrans = function(transition, params){
+    params = $.splat(params);
+    return $.extend(transition, {
+      easeIn: function(pos){
+        return transition(pos, params);
+      },
+      easeOut: function(pos){
+        return 1 - transition(1 - pos, params);
+      },
+      easeInOut: function(pos){
+        return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
+            2 * (1 - pos), params)) / 2;
+      }
+    });
+  };
+
+  var transitions = {
+
+    Pow: function(p, x){
+      return Math.pow(p, x[0] || 6);
+    },
+
+    Expo: function(p){
+      return Math.pow(2, 8 * (p - 1));
+    },
+
+    Circ: function(p){
+      return 1 - Math.sin(Math.acos(p));
+    },
+
+    Sine: function(p){
+      return 1 - Math.sin((1 - p) * Math.PI / 2);
+    },
+
+    Back: function(p, x){
+      x = x[0] || 1.618;
+      return Math.pow(p, 2) * ((x + 1) * p - x);
+    },
+
+    Bounce: function(p){
+      var value;
+      for ( var a = 0, b = 1; 1; a += b, b /= 2) {
+        if (p >= (7 - 4 * a) / 11) {
+          value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+          break;
+        }
+      }
+      return value;
+    },
+
+    Elastic: function(p, x){
+      return Math.pow(2, 10 * --p)
+          * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
+    }
+
+  };
+
+  $.each(transitions, function(val, key){
+    Trans[key] = makeTrans(val);
+  });
+
+  $.each( [
+      'Quad', 'Cubic', 'Quart', 'Quint'
+  ], function(elem, i){
+    Trans[elem] = makeTrans(function(p){
+      return Math.pow(p, [
+        i + 2
+      ]);
+    });
+  });
+
+})();
+
+/*
+   A Class that can perform animations for generic objects.
+
+   If you are looking for animation transitions please take a look at the <Trans> object.
+
+   Used by:
+
+   <Graph.Plot>
+   
+   Based on:
+   
+   The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
+
+*/
+
+var Animation = new Class( {
+
+  initialize: function(options){
+    this.setOptions(options);
+  },
+
+  setOptions: function(options){
+    var opt = {
+      duration: 2500,
+      fps: 40,
+      transition: Trans.Quart.easeInOut,
+      compute: $.empty,
+      complete: $.empty,
+      link: 'ignore'
+    };
+    this.opt = $.merge(opt, options || {});
+    return this;
+  },
+
+  step: function(){
+    var time = $.time(), opt = this.opt;
+    if (time < this.time + opt.duration) {
+      var delta = opt.transition((time - this.time) / opt.duration);
+      opt.compute(delta);
+    } else {
+      this.timer = clearInterval(this.timer);
+      opt.compute(1);
+      opt.complete();
+    }
+  },
+
+  start: function(){
+    if (!this.check())
+      return this;
+    this.time = 0;
+    this.startTimer();
+    return this;
+  },
+
+  startTimer: function(){
+    var that = this, fps = this.opt.fps;
+    if (this.timer)
+      return false;
+    this.time = $.time() - this.time;
+    this.timer = setInterval((function(){
+      that.step();
+    }), Math.round(1000 / fps));
+    return true;
+  },
+
+  pause: function(){
+    this.stopTimer();
+    return this;
+  },
+
+  resume: function(){
+    this.startTimer();
+    return this;
+  },
+
+  stopTimer: function(){
+    if (!this.timer)
+      return false;
+    this.time = $.time() - this.time;
+    this.timer = clearInterval(this.timer);
+    return true;
+  },
+
+  check: function(){
+    if (!this.timer)
+      return true;
+    if (this.opt.link == 'cancel') {
+      this.stopTimer();
+      return true;
+    }
+    return false;
+  }
+});
+
+
+var Options = function() {
+  var args = arguments;
+  for(var i=0, l=args.length, ans={}; i<l; i++) {
+    var opt = Options[args[i]];
+    if(opt.$extend) {
+      $.extend(ans, opt);
+    } else {
+      ans[args[i]] = opt;  
+    }
+  }
+  return ans;
+};
+
+/*
+ * File: Options.Canvas.js
+ *
+*/
+
+/*
+  Object: Options.Canvas
+  
+  These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
+  and other more advanced options.
+  
+  Syntax:
+  
+  (start code js)
+
+  Options.Canvas = {
+    injectInto: 'id',
+    type: '2D', //'3D'
+    width: false,
+    height: false,
+    useCanvas: false,
+    withLabels: true,
+    background: false
+  };  
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    injectInto: 'someContainerId',
+    width: 500,
+    height: 700
+  });
+  (end code)
+  
+  Parameters:
+  
+  injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
+  type - (string) Context type. Default's 2D but can be 3D for webGL enabled browsers.
+  width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
+  height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
+  useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
+  withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
+  background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
+*/
+
+Options.Canvas = {
+    $extend: true,
+    
+    injectInto: 'id',
+    type: '2D',
+    width: false,
+    height: false,
+    useCanvas: false,
+    withLabels: true,
+    background: false,
+    
+    Scene: {
+      Lighting: {
+        enable: false,
+        ambient: [1, 1, 1],
+        directional: {
+          direction: { x: -100, y: -100, z: -100 },
+          color: [0.5, 0.3, 0.1]
+        }
+      }
+    }
+};
+
+/*
+ * File: Options.Node.js
+ *
+*/
+
+/*
+  Object: Options.Node
+
+  Provides Node rendering options for Tree and Graph based visualizations.
+
+  Syntax:
+    
+  (start code js)
+  Options.Node = {
+    overridable: false,
+    type: 'circle',
+    color: '#ccb',
+    alpha: 1,
+    dim: 3,
+    height: 20,
+    width: 90,
+    autoHeight: false,
+    autoWidth: false,
+    lineWidth: 1,
+    transform: true,
+    align: "center",
+    angularWidth:1,
+    span:1,
+    CanvasStyles: {}
+  };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    Node: {
+      overridable: true,
+      width: 30,
+      autoHeight: true,
+      type: 'rectangle'
+    }
+  });
+  (end code)
+  
+  Parameters:
+
+  overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
+  type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
+  color - (string) Default's *#ccb*. Node color.
+  alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
+  dim - (number) Default's *3*. An extra parameter used by 'circle', 'square', 'triangle' and 'star' node types. Depending on each shape, this parameter can set the radius of a circle, half the length of the side of a square, half the base and half the height of a triangle or the length of a side of a star (concave decagon).
+  height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
+  width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
+  autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
+  autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
+  lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
+  transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
+  align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
+  angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
+  span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
+  CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
+
+*/
+Options.Node = {
+  $extend: false,
+  
+  overridable: false,
+  type: 'circle',
+  color: '#ccb',
+  alpha: 1,
+  dim: 3,
+  height: 20,
+  width: 90,
+  autoHeight: false,
+  autoWidth: false,
+  lineWidth: 1,
+  transform: true,
+  align: "center",
+  angularWidth:1,
+  span:1,
+  //Raw canvas styles to be
+  //applied to the context instance
+  //before plotting a node
+  CanvasStyles: {}
+};
+
+
+/*
+ * File: Options.Edge.js
+ *
+*/
+
+/*
+  Object: Options.Edge
+
+  Provides Edge rendering options for Tree and Graph based visualizations.
+
+  Syntax:
+    
+  (start code js)
+  Options.Edge = {
+    overridable: false,
+    type: 'line',
+    color: '#ccb',
+    lineWidth: 1,
+    dim:15,
+    alpha: 1,
+    CanvasStyles: {}
+  };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    Edge: {
+      overridable: true,
+      type: 'line',
+      color: '#fff',
+      CanvasStyles: {
+        shadowColor: '#ccc',
+        shadowBlur: 10
+      }
+    }
+  });
+  (end code)
+  
+  Parameters:
+    
+   overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
+   type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
+   color - (string) Default's '#ccb'. Edge color.
+   lineWidth - (number) Default's *1*. Line/Edge width.
+   alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
+   dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
+   epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
+   CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
+
+  See also:
+   
+   If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
+*/
+Options.Edge = {
+  $extend: false,
+  
+  overridable: false,
+  type: 'line',
+  color: '#ccb',
+  lineWidth: 1,
+  dim:15,
+  alpha: 1,
+  epsilon: 7,
+
+  //Raw canvas styles to be
+  //applied to the context instance
+  //before plotting an edge
+  CanvasStyles: {}
+};
+
+
+/*
+ * File: Options.Fx.js
+ *
+*/
+
+/*
+  Object: Options.Fx
+
+  Provides animation options like duration of the animations, frames per second and animation transitions.  
+
+  Syntax:
+  
+  (start code js)
+    Options.Fx = {
+      fps:40,
+      duration: 2500,
+      transition: $jit.Trans.Quart.easeInOut,
+      clearCanvas: true
+    };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    duration: 1000,
+    fps: 35,
+    transition: $jit.Trans.linear
+  });
+  (end code)
+  
+  Parameters:
+  
+  clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
+  duration - (number) Default's *2500*. Duration of the animation in milliseconds.
+  fps - (number) Default's *40*. Frames per second.
+  transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
+  
+  Object: $jit.Trans
+  
+  This object is used for specifying different animation transitions in all visualizations.
+
+  There are many different type of animation transitions.
+
+  linear:
+
+  Displays a linear transition
+
+  >Trans.linear
+  
+  (see Linear.png)
+
+  Quad:
+
+  Displays a Quadratic transition.
+
+  >Trans.Quad.easeIn
+  >Trans.Quad.easeOut
+  >Trans.Quad.easeInOut
+  
+ (see Quad.png)
+
+ Cubic:
+
+ Displays a Cubic transition.
+
+ >Trans.Cubic.easeIn
+ >Trans.Cubic.easeOut
+ >Trans.Cubic.easeInOut
+
+ (see Cubic.png)
+
+ Quart:
+
+ Displays a Quartetic transition.
+
+ >Trans.Quart.easeIn
+ >Trans.Quart.easeOut
+ >Trans.Quart.easeInOut
+
+ (see Quart.png)
+
+ Quint:
+
+ Displays a Quintic transition.
+
+ >Trans.Quint.easeIn
+ >Trans.Quint.easeOut
+ >Trans.Quint.easeInOut
+
+ (see Quint.png)
+
+ Expo:
+
+ Displays an Exponential transition.
+
+ >Trans.Expo.easeIn
+ >Trans.Expo.easeOut
+ >Trans.Expo.easeInOut
+
+ (see Expo.png)
+
+ Circ:
+
+ Displays a Circular transition.
+
+ >Trans.Circ.easeIn
+ >Trans.Circ.easeOut
+ >Trans.Circ.easeInOut
+
+ (see Circ.png)
+
+ Sine:
+
+ Displays a Sineousidal transition.
+
+ >Trans.Sine.easeIn
+ >Trans.Sine.easeOut
+ >Trans.Sine.easeInOut
+
+ (see Sine.png)
+
+ Back:
+
+ >Trans.Back.easeIn
+ >Trans.Back.easeOut
+ >Trans.Back.easeInOut
+
+ (see Back.png)
+
+ Bounce:
+
+ Bouncy transition.
+
+ >Trans.Bounce.easeIn
+ >Trans.Bounce.easeOut
+ >Trans.Bounce.easeInOut
+
+ (see Bounce.png)
+
+ Elastic:
+
+ Elastic curve.
+
+ >Trans.Elastic.easeIn
+ >Trans.Elastic.easeOut
+ >Trans.Elastic.easeInOut
+
+ (see Elastic.png)
+ Based on:
+     
+ Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
+
+
+*/
+Options.Fx = {
+  $extend: true,
+  
+  fps:40,
+  duration: 2500,
+  transition: $jit.Trans.Quart.easeInOut,
+  clearCanvas: true
+};
+
+/*
+ * File: Options.Label.js
+ *
+*/
+/*
+  Object: Options.Label
+
+  Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
+
+  Syntax:
+  
+  (start code js)
+    Options.Label = {
+      overridable: false,
+      type: 'HTML', //'SVG', 'Native'
+      style: ' ',
+      size: 10,
+      family: 'sans-serif',
+      textAlign: 'center',
+      textBaseline: 'alphabetic',
+      color: '#fff'
+    };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    Label: {
+      type: 'Native',
+      size: 11,
+      color: '#ccc'
+    }
+  });
+  (end code)
+  
+  Parameters:
+    
+  overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
+  type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
+  style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+  size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+  family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+  color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+*/
+Options.Label = {
+  $extend: false,
+  
+  overridable: false,
+  type: 'HTML', //'SVG', 'Native'
+  style: ' ',
+  size: 10,
+  family: 'sans-serif',
+  textAlign: 'center',
+  textBaseline: 'alphabetic',
+  color: '#fff'
+};
+
+
+/*
+ * File: Options.Tips.js
+ *
+ */
+
+/*
+  Object: Options.Tips
+  
+  Tips options
+  
+  Syntax:
+    
+  (start code js)
+  Options.Tips = {
+    enable: false,
+    type: 'auto',
+    offsetX: 20,
+    offsetY: 20,
+    onShow: $.empty,
+    onHide: $.empty
+  };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    Tips: {
+      enable: true,
+      type: 'Native',
+      offsetX: 10,
+      offsetY: 10,
+      onShow: function(tip, node) {
+        tip.innerHTML = node.name;
+      }
+    }
+  });
+  (end code)
+
+  Parameters:
+
+  enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class. 
+  type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
+  offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
+  offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
+  onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
+  onHide() - This callack is used when hiding a tooltip.
+
+*/
+Options.Tips = {
+  $extend: false,
+  
+  enable: false,
+  type: 'auto',
+  offsetX: 20,
+  offsetY: 20,
+  force: false,
+  onShow: $.empty,
+  onHide: $.empty
+};
+
+
+/*
+ * File: Options.NodeStyles.js
+ *
+ */
+
+/*
+  Object: Options.NodeStyles
+  
+  Apply different styles when a node is hovered or selected.
+  
+  Syntax:
+    
+  (start code js)
+  Options.NodeStyles = {
+    enable: false,
+    type: 'auto',
+    stylesHover: false,
+    stylesClick: false
+  };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    NodeStyles: {
+      enable: true,
+      type: 'Native',
+      stylesHover: {
+        dim: 30,
+        color: '#fcc'
+      },
+      duration: 600
+    }
+  });
+  (end code)
+
+  Parameters:
+  
+  enable - (boolean) Default's *false*. Whether to enable this option.
+  type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
+  stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
+  stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
+*/
+
+Options.NodeStyles = {
+  $extend: false,
+  
+  enable: false,
+  type: 'auto',
+  stylesHover: false,
+  stylesClick: false
+};
+
+
+/*
+ * File: Options.Events.js
+ *
+*/
+
+/*
+  Object: Options.Events
+  
+  Configuration for adding mouse/touch event handlers to Nodes.
+  
+  Syntax:
+  
+  (start code js)
+  Options.Events = {
+    enable: false,
+    enableForEdges: false,
+    type: 'auto',
+    onClick: $.empty,
+    onRightClick: $.empty,
+    onMouseMove: $.empty,
+    onMouseEnter: $.empty,
+    onMouseLeave: $.empty,
+    onDragStart: $.empty,
+    onDragMove: $.empty,
+    onDragCancel: $.empty,
+    onDragEnd: $.empty,
+    onTouchStart: $.empty,
+    onTouchMove: $.empty,
+    onTouchEnd: $.empty,
+    onTouchCancel: $.empty,
+    onMouseWheel: $.empty
+  };
+  (end code)
+  
+  Example:
+  
+  (start code js)
+  var viz = new $jit.Viz({
+    Events: {
+      enable: true,
+      onClick: function(node, eventInfo, e) {
+        viz.doSomething();
+      },
+      onMouseEnter: function(node, eventInfo, e) {
+        viz.canvas.getElement().style.cursor = 'pointer';
+      },
+      onMouseLeave: function(node, eventInfo, e) {
+        viz.canvas.getElement().style.cursor = '';
+      }
+    }
+  });
+  (end code)
+  
+  Parameters:
+  
+  enable - (boolean) Default's *false*. Whether to enable the Event system.
+  enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
+  type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
+  onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner).  *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+  onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
+  onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
+  onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
+  onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
+  onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
+  onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
+*/
+
+Options.Events = {
+  $extend: false,
+  
+  enable: false,
+  enableForEdges: false,
+  type: 'auto',
+  onClick: $.empty,
+  onRightClick: $.empty,
+  onMouseMove: $.empty,
+  onMouseEnter: $.empty,
+  onMouseLeave: $.empty,
+  onDragStart: $.empty,
+  onDragMove: $.empty,
+  onDragCancel: $.empty,
+  onDragEnd: $.empty,
+  onTouchStart: $.empty,
+  onTouchMove: $.empty,
+  onTouchEnd: $.empty,
+  onMouseWheel: $.empty
+};
+
+/*
+ * File: Options.Navigation.js
+ *
+*/
+
+/*
+  Object: Options.Navigation
+  
+  Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
+  by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
+  
+  Syntax:
+  
+  (start code js)
+
+  Options.Navigation = {
+    enable: false,
+    type: 'auto',
+    panning: false, //true, 'avoid nodes'
+    zooming: false
+  };
+  
+  (end code)
+  
+  Example:
+    
+  (start code js)
+  var viz = new $jit.Viz({
+    Navigation: {
+      enable: true,
+      panning: 'avoid nodes',
+      zooming: 20
+    }
+  });
+  (end code)
+  
+  Parameters:
+  
+  enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
+  type - (string) Default's 'auto'. Whether to attach the navigation events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. When 'auto' set when you let the <Options.Label> *type* parameter decide this.
+  panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
+  zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
+  
+*/
+
+Options.Navigation = {
+  $extend: false,
+  
+  enable: false,
+  type: 'auto',
+  panning: false, //true | 'avoid nodes'
+  zooming: false
+};
+
+/*
+ * File: Options.Controller.js
+ *
+*/
+
+/*
+  Object: Options.Controller
+  
+  Provides controller methods. Controller methods are callback functions that get called at different stages 
+  of the animation, computing or plotting of the visualization.
+  
+  Implemented by:
+    
+  All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
+  
+  Syntax:
+  
+  (start code js)
+
+  Options.Controller = {
+    onBeforeCompute: $.empty,
+    onAfterCompute:  $.empty,
+    onCreateLabel:   $.empty,
+    onPlaceLabel:    $.empty,
+    onComplete:      $.empty,
+    onBeforePlotLine:$.empty,
+    onAfterPlotLine: $.empty,
+    onBeforePlotNode:$.empty,
+    onAfterPlotNode: $.empty,
+    request:         false
+  };
+  
+  (end code)
+  
+  Example:
+    
+  (start code js)
+  var viz = new $jit.Viz({
+    onBeforePlotNode: function(node) {
+      if(node.selected) {
+        node.setData('color', '#ffc');
+      } else {
+        node.removeData('color');
+      }
+    },
+    onBeforePlotLine: function(adj) {
+      if(adj.nodeFrom.selected && adj.nodeTo.selected) {
+        adj.setData('color', '#ffc');
+      } else {
+        adj.removeData('color');
+      }
+    },
+    onAfterCompute: function() {
+      alert("computed!");
+    }
+  });
+  (end code)
+  
+  Parameters:
+
+   onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
+   onAfterCompute() - This method is triggered after all animations or computations ended.
+   onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
+   onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
+   onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
+   onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
+   onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
+   onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
+   onBeforeRemoveNode(node) - This method is triggered right before removing each <Graph.Node>.
+   
+    *Used in <ST>, <TM.Base> and <Icicle> visualizations*
+    
+    request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.  
+ */
+Options.Controller = {
+  $extend: true,
+  
+  onBeforeCompute:   $.empty,
+  onAfterCompute:    $.empty,
+  onCreateLabel:     $.empty,
+  onPlaceLabel:      $.empty,
+  onComplete:        $.empty,
+  onBeforePlotLine:  $.empty,
+  onAfterPlotLine:   $.empty,
+  onBeforePlotNode:  $.empty,
+  onAfterPlotNode:   $.empty,
+  onBeforeRemoveNode:$.empty,
+  request:         false
+};
+
+
+/*
+ * File: Extras.js
+ * 
+ * Provides Extras such as Tips and Style Effects.
+ * 
+ * Description:
+ * 
+ * Provides the <Tips> and <NodeStyles> classes and functions.
+ *
+ */
+
+/*
+ * Manager for mouse events (clicking and mouse moving).
+ * 
+ * This class is used for registering objects implementing onClick
+ * and onMousemove methods. These methods are called when clicking or
+ * moving the mouse around  the Canvas.
+ * For now, <Tips> and <NodeStyles> are classes implementing these methods.
+ * 
+ */
+var MultiExtrasInitializer = {
+  initialize: function(className, viz) {
+    this.viz = viz;
+    this.canvas = viz.canvas;
+    this.config = viz.config[className];
+    this.nodeTypes = viz.fx.nodeTypes;
+    var type = this.config.type;
+    this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
+    this.labelContainer = this.dom && viz.labels.getLabelContainer();
+    this.isEnabled() && this.initializePost();
+  },
+  initializePost: $.empty,
+  setAsProperty: $.lambda(false),
+  isEnabled: function() {
+    return this.config.enable;
+  },
+  isLabel: function(e, win, group) {
+    e = $.event.get(e, win);
+    var labelContainer = this.labelContainer,
+        target = e.target || e.srcElement,
+        related = e.relatedTarget;
+    if(group) {
+      return related && related == this.viz.canvas.getCtx().canvas 
+          && !!target && this.isDescendantOf(target, labelContainer);
+    } else {
+      return this.isDescendantOf(target, labelContainer);
+    }
+  },
+  isDescendantOf: function(elem, par) {
+    while(elem && elem.parentNode) {
+      if(elem.parentNode == par)
+        return elem;
+      elem = elem.parentNode;
+    }
+    return false;
+  }
+};
+
+var MultiEventsInterface = {
+  onMouseUp: $.empty,
+  onMouseDown: $.empty,
+  onMouseMove: $.empty,
+  onMouseOver: $.empty,
+  onMouseOut: $.empty,
+  onMouseWheel: $.empty,
+  onTouchStart: $.empty,
+  onTouchMove: $.empty,
+  onTouchEnd: $.empty,
+  onTouchCancel: $.empty
+};
+
+var MouseEventsManager = new Class({
+  initialize: function(viz) {
+    this.viz = viz;
+    this.canvas = viz.canvas;
+    this.node = false;
+    this.edge = false;
+    this.registeredObjects = [];
+    this.attachEvents();
+  },
+  
+  attachEvents: function() {
+    var htmlCanvas = this.canvas.getElement(), 
+        that = this;
+    htmlCanvas.oncontextmenu = $.lambda(false);
+    $.addEvents(htmlCanvas, {
+      'mouseup': function(e, win) {
+        var event = $.event.get(e, win);
+        that.handleEvent('MouseUp', e, win, 
+            that.makeEventObject(e, win), 
+            $.event.isRightClick(event));
+      },
+      'mousedown': function(e, win) {
+        var event = $.event.get(e, win);
+        that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
+            $.event.isRightClick(event));
+      },
+      'mousemove': function(e, win) {
+        that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
+      },
+      'mouseover': function(e, win) {
+        that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
+      },
+      'mouseout': function(e, win) {
+        that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
+      },
+      'touchstart': function(e, win) {
+        that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
+      },
+      'touchmove': function(e, win) {
+        that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
+      },
+      'touchend': function(e, win) {
+        that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
+      }
+    });
+    //attach mousewheel event
+    var handleMouseWheel = function(e, win) {
+      var event = $.event.get(e, win);
+      var wheel = $.event.getWheel(event);
+      that.handleEvent('MouseWheel', e, win, wheel);
+    };
+    //TODO(nico): this is a horrible check for non-gecko browsers!
+    if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
+      $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
+    } else {
+      htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
+    }
+  },
+  
+  register: function(obj) {
+    this.registeredObjects.push(obj);
+  },
+  
+  handleEvent: function() {
+    var args = Array.prototype.slice.call(arguments),
+        type = args.shift();
+    for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
+      regs[i]['on' + type].apply(regs[i], args);
+    }
+  },
+  
+  makeEventObject: function(e, win) {
+    var that = this,
+        graph = this.viz.graph,
+        fx = this.viz.fx,
+        ntypes = fx.nodeTypes,
+        etypes = fx.edgeTypes;
+    return {
+      pos: false,
+      node: false,
+      edge: false,
+      contains: false,
+      getNodeCalled: false,
+      getEdgeCalled: false,
+      getPos: function() {
+        //TODO(nico): check why this can't be cache anymore when using edge detection
+        //if(this.pos) return this.pos;
+        var canvas = that.viz.canvas,
+            s = canvas.getSize(),
+            p = canvas.getPos(),
+            ox = canvas.translateOffsetX,
+            oy = canvas.translateOffsetY,
+            sx = canvas.scaleOffsetX,
+            sy = canvas.scaleOffsetY,
+            pos = $.event.getPos(e, win);
+        this.pos = {
+          x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
+          y: (pos.y - p.y - s.height/2 - oy) * 1/sy
+        };
+        return this.pos;
+      },
+      getNode: function() {
+        if(this.getNodeCalled) return this.node;
+        this.getNodeCalled = true;
+        for(var id in graph.nodes) {
+          var n = graph.nodes[id],
+              geom = n && ntypes[n.getData('type')],
+              contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
+          if(contains) {
+            this.contains = contains;
+            return that.node = this.node = n;
+          }
+        }
+        return that.node = this.node = false;
+      },
+      getEdge: function() {
+               if(this.getEdgeCalled) return this.edge;
+               this.getEdgeCalled = true;
+
+                 var checkDupEdge = function(e) {
+                       var skip = false;
+                       for(var d in graph.dup) {
+                               var ee = graph.dup[d];
+                               if(e.nodeTo.id == ee.nodeTo.id && e.nodeFrom.id == ee.nodeFrom.id) {
+                                       if(e.portTo == ee.portTo && e.portFrom == ee.portFrom) {
+                                               skip = true;
+                                               break; // NOTE: does this break the outer for loop?
+                                       }
+                               }
+                       }
+                       return skip;
+                 };
+  
+               // you want to go through all the edges in this graph
+               for(var id in graph.edges) {
+                       var edgeFrom = graph.edges[id];
+
+                       for(var edgeId in edgeFrom) {
+                               // proceed with contains check
+                               var total = edgeFrom[edgeId].length;
+                               if (total == 1) {
+                                       var e = edgeFrom[edgeId][0];
+
+                                       if(checkDupEdge(e)) continue; // skip this edge if it is a dup
+
+                                       var geom = e && etypes[e.getData('type')],
+                                               contains = geom && geom.contains && geom.contains.call(fx, e, 0, this.getPos(), that.canvas);
+
+                                       if (contains) {
+                                               this.contains = contains;
+                                               return that.edge = this.edge = e;
+                                       }
+                               } else {
+                                       for(var idj in edgeFrom[edgeId]) {
+                                               var e = edgeFrom[edgeId][idj];
+
+                                               if(checkDupEdge(e)) continue; // skip this edge if it is a dup
+
+                                               var alpha = parseInt(idj,10);
+                                               var start = (0.5-(total/2));
+                                               alpha += start;
+
+                                               var geom = e && etypes[e.getData('type')],
+                                                       contains = geom && geom.contains && geom.contains.call(fx, e, alpha, this.getPos(), that.canvas);
+                                               if (contains) {
+                                                       this.contains = contains;
+                                                       return that.edge = this.edge = e;
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return that.edge = this.edge = false;
+
+               /*
+        if(this.getEdgeCalled) return this.edge;
+        this.getEdgeCalled = true;
+        var hashset = {};
+        for(var id in graph.edges) {
+          var edgeFrom = graph.edges[id];
+          hashset[id] = true;
+          for(var edgeId in edgeFrom) {
+            if(edgeId in hashset) continue;
+            var e = edgeFrom[edgeId],
+                geom = e && etypes[e.getData('type')],
+                contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
+            if(contains) {
+              this.contains = contains;
+              return that.edge = this.edge = e;
+            }
+          }
+        }
+        return that.edge = this.edge = false;
+               */
+      },
+      getContains: function() {
+        if(this.getNodeCalled) return this.contains;
+        this.getNode();
+        return this.contains;
+      }
+    };
+  }
+});
+
+/* 
+ * Provides the initialization function for <NodeStyles> and <Tips> implemented 
+ * by all main visualizations.
+ *
+ */
+var MultiExtras = {
+  initializeExtras: function() {
+    var mem = new MouseEventsManager(this), that = this;
+    $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
+      var obj = new MultiExtras.Classes[k](k, that);
+      if(obj.isEnabled()) {
+        mem.register(obj);
+      }
+      if(obj.setAsProperty()) {
+        that[k.toLowerCase()] = obj;
+      }
+    });
+  }   
+};
+
+MultiExtras.Classes = {};
+/*
+  Class: Events
+   
+  This class defines an Event API to be accessed by the user.
+  The methods implemented are the ones defined in the <Options.Events> object.
+*/
+
+MultiExtras.Classes.Events = new Class({
+  Implements: [MultiExtrasInitializer, MultiEventsInterface],
+  
+  initializePost: function() {
+    this.fx = this.viz.fx;
+    this.ntypes = this.viz.fx.nodeTypes;
+    this.etypes = this.viz.fx.edgeTypes;
+    
+    this.hovered = false;
+    this.pressed = false;
+    this.touched = false;
+
+    this.touchMoved = false;
+    this.moved = false;
+    
+  },
+  
+  setAsProperty: $.lambda(true),
+
+  onMouseUp: function(e, win, event, isRightClick) {
+    var evt = $.event.get(e, win);
+    if(!this.moved) {
+      if(isRightClick) {
+        this.config.onRightClick(this.hovered, event, evt);
+      } else {
+        this.config.onClick(this.pressed, event, evt);
+      }
+    }
+    if(this.pressed) {
+      if(this.moved) {
+        this.config.onDragEnd(this.pressed, event, evt);
+      } else {
+        this.config.onDragCancel(this.pressed, event, evt);
+      }
+      this.pressed = this.moved = false;
+    }
+  },
+
+  onMouseOut: function(e, win, event) {
+   //mouseout a label
+   var evt = $.event.get(e, win), label;
+   if(this.dom && (label = this.isLabel(e, win, true))) {
+     this.config.onMouseLeave(this.viz.graph.getNode(label.id),
+                              event, evt);
+     this.hovered = false;
+     return;
+   }
+   //mouseout canvas
+   var rt = evt.relatedTarget,
+       canvasWidget = this.canvas.getElement();
+   while(rt && rt.parentNode) {
+     if(canvasWidget == rt.parentNode) return;
+     rt = rt.parentNode;
+   }
+   if(this.hovered) {
+     this.config.onMouseLeave(this.hovered,
+         event, evt);
+     this.hovered = false;
+   }
+  },
+  
+  onMouseOver: function(e, win, event) {
+    //mouseover a label
+    var evt = $.event.get(e, win), label;
+    if(this.dom && (label = this.isLabel(e, win, true))) {
+      this.hovered = this.viz.graph.getNode(label.id);
+      this.config.onMouseEnter(this.hovered,
+                               event, evt);
+    }
+  },
+  
+  onMouseMove: function(e, win, event) {
+   var label, evt = $.event.get(e, win);
+   if(this.pressed) {
+     this.moved = true;
+     this.config.onDragMove(this.pressed, event, evt);
+     return;
+   }
+   if(this.dom) {
+     this.config.onMouseMove(this.hovered, event, evt);
+   } else {
+     if(this.hovered) { // if there is a hovered element
+               if(this.hovered.id == undefined) { // if this is an edge
+                       var hn = this.hovered;
+                       var geom = this.etypes[hn.getData('type')];
+                       var contains = false;
+
+                       // find alpha and total
+                       var list = this.viz.graph.edges[hn.nodeFrom.id][hn.nodeTo.id];
+                       if (list != undefined || list.length != 0) {
+                               var total = list.length;
+                               for(var idj = 0; idj < total; idj++) {
+                                       var e = list[idj];
+
+                                       // check if we need to skip this dup edge
+                                       // NOTE: is this even needed, if getEdge() already fulfills this action?
+                                       var skip = false;
+                                       for(var d in this.viz.graph.dup) {
+                                               var ee = this.viz.graph.dup[d];
+                                               if(e.nodeTo.id == ee.nodeTo.id && e.nodeFrom.id == ee.nodeFrom.id) {
+                                                       if(e.portTo == ee.portTo && e.portFrom == ee.portFrom) {
+                                                               skip = true;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                                       if(skip) continue; // skip this edge if it is a dup
+
+                                       var alpha = parseInt(idj,10);
+                                       var start = (0.5-(total/2));
+                                       alpha += start;
+
+                                       if(list[idj].portFrom == hn.portFrom
+                                       && list[idj].portTo == hn.portTo) {
+                                               contains = geom.contains.call(this.fx, hn, alpha, event.getPos(), this.canvas);
+                                               break;
+                                       }
+                               }
+                       }
+
+                       if (contains) {
+                               this.config.onMouseEnter(this.hovered, event, evt);
+                               return;
+                       } else {
+                               this.config.onMouseLeave(hn, event, evt);
+                               this.hovered = false;
+                       }
+               } else { // if this is a node
+                  var hn = this.hovered;
+                  var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
+                  var contains = geom && geom.contains 
+                        && geom.contains.call(this.fx, hn, event.getPos());
+                  if(contains) {
+                        this.config.onMouseMove(hn, event, evt);
+                        return;
+                  } else {
+                        this.config.onMouseLeave(hn, event, evt);
+                        this.hovered = false;
+                  }
+                }
+        }
+
+     if(this.hovered = event.getNode()) {
+       this.config.onMouseEnter(this.hovered, event, evt);
+        } else if (this.hovered = event.getEdge()) { // event for edges
+               this.config.onMouseEnter(this.hovered, event, evt);
+     } else {
+       this.config.onMouseMove(false, event, evt);
+     }
+   }
+  },
+  
+  onMouseWheel: function(e, win, delta) {
+    this.config.onMouseWheel(delta, $.event.get(e, win));
+  },
+  
+  onMouseDown: function(e, win, event) {
+    var evt = $.event.get(e, win), label;
+    if(this.dom) {
+      if(label = this.isLabel(e, win)) {
+        this.pressed = this.viz.graph.getNode(label.id);
+      }
+    } else {
+      this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
+    }
+    this.pressed && this.config.onDragStart(this.pressed, event, evt);
+  },
+  
+  onTouchStart: function(e, win, event) {
+    var evt = $.event.get(e, win), label;
+    if(this.dom && (label = this.isLabel(e, win))) {
+      this.touched = this.viz.graph.getNode(label.id);
+    } else {
+      this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
+    }
+    this.touched && this.config.onTouchStart(this.touched, event, evt);
+  },
+  
+  onTouchMove: function(e, win, event) {
+    var evt = $.event.get(e, win);
+    if(this.touched) {
+      this.touchMoved = true;
+      this.config.onTouchMove(this.touched, event, evt);
+    }
+  },
+  
+  onTouchEnd: function(e, win, event) {
+    var evt = $.event.get(e, win);
+    if(this.touched) {
+      if(this.touchMoved) {
+        this.config.onTouchEnd(this.touched, event, evt);
+      } else {
+        this.config.onTouchCancel(this.touched, event, evt);
+      }
+      this.touched = this.touchMoved = false;
+    }
+  }
+});
+
+/*
+   Class: Tips
+    
+   A class containing tip related functions. This class is used internally.
+   
+   Used by:
+   
+   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
+   
+   See also:
+   
+   <Options.Tips>
+*/
+
+MultiExtras.Classes.Tips = new Class({
+  Implements: [MultiExtrasInitializer, MultiEventsInterface],
+  
+  initializePost: function() {
+    //add DOM tooltip
+    if(document.body) {
+      var tip = $('_tooltip') || document.createElement('div');
+      tip.id = '_tooltip';
+      tip.className = 'tip';
+      $.extend(tip.style, {
+        position: 'absolute',
+        display: 'none',
+        zIndex: 13000
+      });
+      document.body.appendChild(tip);
+      this.tip = tip;
+      this.node = false;
+    }
+  },
+  
+  setAsProperty: $.lambda(true),
+  
+  onMouseOut: function(e, win) {
+    //mouseout a label
+    var evt = $.event.get(e, win);
+    if(this.dom && this.isLabel(e, win, true)) {
+      this.hide(true);
+      return;
+    }
+    //mouseout canvas
+    var rt = e.relatedTarget,
+        canvasWidget = this.canvas.getElement();
+    while(rt && rt.parentNode) {
+      if(canvasWidget == rt.parentNode) return;
+      rt = rt.parentNode;
+    }
+    this.hide(false);
+  },
+  
+  onMouseOver: function(e, win) {
+    //mouseover a label
+    var label;
+    if(this.dom && (label = this.isLabel(e, win, false))) {
+      this.node = this.viz.graph.getNode(label.id);
+      this.config.onShow(this.tip, this.node, label);
+    }
+  },
+  
+  onMouseMove: function(e, win, opt) {
+    if(this.dom && this.isLabel(e, win)) {
+      this.setTooltipPosition($.event.getPos(e, win));
+    }
+    if(!this.dom) {
+      var node = opt.getNode();
+         var edge = opt.getEdge();
+      if(!node && !edge) { // additionally for edge
+        this.hide(true);
+        return;
+      }
+      /*if(this.config.force || !this.node || this.node.id != node.id) {
+        this.node = node;
+        this.config.onShow(this.tip, node, opt.getContains());
+      }*/
+               if (node != false) {    
+                       if (this.config.force || !this.node || this.node.id != node.id) {
+                               this.node = node;
+                               this.edge = false;
+                               this.config.onShow(this.tip, node, opt.getContains());
+                       }
+               } else if (edge != false) {
+                       this.edge = edge;
+                       this.node = false;
+                       this.config.onShow(this.tip, edge, opt.getContains());
+               }
+      this.setTooltipPosition($.event.getPos(e, win));
+    }
+  },
+  
+  setTooltipPosition: function(pos) {
+    var tip = this.tip, 
+        style = tip.style, 
+        cont = this.config;
+    style.display = '';
+    //get viewport dimensions
+    var elem = document.compatMode === "CSS1Compat" && document.documentElement ||
+               document.body ||
+               document.documentElement;
+    var view = {
+      'width': elem.clientWidth,
+      'height': elem.clientHeight,
+      'x': window.pageXOffset ||
+           document.documentElement && document.documentElement.scrollLeft ||
+           document.body && document.body.scrollLeft ||
+           0,
+      'y': window.pageYOffset ||
+           document.documentElement && document.documentElement.scrollTop ||
+           document.body && document.body.scrollTop ||
+           0
+    };
+    //get tooltip dimensions
+    var obj = {
+      'width': tip.offsetWidth,
+      'height': tip.offsetHeight  
+    };
+    //set tooltip position
+    var x = cont.offsetX, y = cont.offsetY;
+    style.top = ((pos.y + obj.height + y > view.height + view.y)?
+        (pos.y - obj.height - y) : pos.y + y) + 'px';
+    style.left = ((pos.x + obj.width + x > view.width + view.x)?
+        (pos.x - obj.width - x) : pos.x + x) + 'px';
+  },
+  
+  hide: function(triggerCallback) {
+    this.tip.style.display = 'none';
+    triggerCallback && this.config.onHide();
+  }
+});
+
+/*
+  Class: NodeStyles
+   
+  Change node styles when clicking or hovering a node. This class is used internally.
+  
+  Used by:
+  
+  <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
+  
+  See also:
+  
+  <Options.NodeStyles>
+*/
+MultiExtras.Classes.NodeStyles = new Class({
+  Implements: [MultiExtrasInitializer, MultiEventsInterface],
+  
+  initializePost: function() {
+    this.fx = this.viz.fx;
+    this.types = this.viz.fx.nodeTypes;
+    this.nStyles = this.config;
+    this.nodeStylesOnHover = this.nStyles.stylesHover;
+    this.nodeStylesOnClick = this.nStyles.stylesClick;
+    this.hoveredNode = false;
+    this.fx.nodeFxAnimation = new Animation();
+    
+    this.down = false;
+    this.move = false;
+  },
+  
+  onMouseOut: function(e, win) {
+    this.down = this.move = false;
+    if(!this.hoveredNode) return;
+    //mouseout a label
+    if(this.dom && this.isLabel(e, win, true)) {
+      this.toggleStylesOnHover(this.hoveredNode, false);
+    }
+    //mouseout canvas
+    var rt = e.relatedTarget,
+        canvasWidget = this.canvas.getElement();
+    while(rt && rt.parentNode) {
+      if(canvasWidget == rt.parentNode) return;
+      rt = rt.parentNode;
+    }
+    this.toggleStylesOnHover(this.hoveredNode, false);
+    this.hoveredNode = false;
+  },
+  
+  onMouseOver: function(e, win) {
+    //mouseover a label
+    var label;
+    if(this.dom && (label = this.isLabel(e, win, true))) {
+      var node = this.viz.graph.getNode(label.id);
+      if(node.selected) return;
+      this.hoveredNode = node;
+      this.toggleStylesOnHover(this.hoveredNode, true);
+    }
+  },
+  
+  onMouseDown: function(e, win, event, isRightClick) {
+    if(isRightClick) return;
+    var label;
+    if(this.dom && (label = this.isLabel(e, win))) {
+      this.down = this.viz.graph.getNode(label.id);
+    } else if(!this.dom) {
+      this.down = event.getNode();
+    }
+    this.move = false;
+  },
+  
+  onMouseUp: function(e, win, event, isRightClick) {
+    if(isRightClick) return;
+    if(!this.move) {
+      this.onClick(event.getNode());
+    }
+    this.down = this.move = false;
+  },
+  
+  getRestoredStyles: function(node, type) {
+    var restoredStyles = {}, 
+        nStyles = this['nodeStylesOn' + type];
+    for(var prop in nStyles) {
+      restoredStyles[prop] = node.styles['$' + prop];
+    }
+    return restoredStyles;
+  },
+  
+  toggleStylesOnHover: function(node, set) {
+    if(this.nodeStylesOnHover) {
+      this.toggleStylesOn('Hover', node, set);
+    }
+  },
+
+  toggleStylesOnClick: function(node, set) {
+    if(this.nodeStylesOnClick) {
+      this.toggleStylesOn('Click', node, set);
+    }
+  },
+  
+  toggleStylesOn: function(type, node, set) {
+    var viz = this.viz;
+    var nStyles = this.nStyles;
+    if(set) {
+      var that = this;
+      if(!node.styles) {
+        node.styles = $.merge(node.data, {});
+      }
+      for(var s in this['nodeStylesOn' + type]) {
+        var $s = '$' + s;
+        if(!($s in node.styles)) {
+            node.styles[$s] = node.getData(s); 
+        }
+      }
+      viz.fx.nodeFx($.extend({
+        'elements': {
+          'id': node.id,
+          'properties': that['nodeStylesOn' + type]
+         },
+         transition: Trans.Quart.easeOut,
+         duration:300,
+         fps:40
+      }, this.config));
+    } else {
+      var restoredStyles = this.getRestoredStyles(node, type);
+      viz.fx.nodeFx($.extend({
+        'elements': {
+          'id': node.id,
+          'properties': restoredStyles
+         },
+         transition: Trans.Quart.easeOut,
+         duration:300,
+         fps:40
+      }, this.config));
+    }
+  },
+
+  onClick: function(node) {
+    if(!node) return;
+    var nStyles = this.nodeStylesOnClick;
+    if(!nStyles) return;
+    //if the node is selected then unselect it
+    if(node.selected) {
+      this.toggleStylesOnClick(node, false);
+      delete node.selected;
+    } else {
+      //unselect all selected nodes...
+      this.viz.graph.eachNode(function(n) {
+        if(n.selected) {
+          for(var s in nStyles) {
+            n.setData(s, n.styles['$' + s], 'end');
+          }
+          delete n.selected;
+        }
+      });
+      //select clicked node
+      this.toggleStylesOnClick(node, true);
+      node.selected = true;
+      delete node.hovered;
+      this.hoveredNode = false;
+    }
+  },
+  
+  onMouseMove: function(e, win, event) {
+    //if mouse button is down and moving set move=true
+    if(this.down) this.move = true;
+    //already handled by mouseover/out
+    if(this.dom && this.isLabel(e, win)) return;
+    var nStyles = this.nodeStylesOnHover;
+    if(!nStyles) return;
+    
+    if(!this.dom) {
+      if(this.hoveredNode) {
+        var geom = this.types[this.hoveredNode.getData('type')];
+        var contains = geom && geom.contains && geom.contains.call(this.fx, 
+            this.hoveredNode, event.getPos());
+        if(contains) return;
+      }
+      var node = event.getNode();
+      //if no node is being hovered then just exit
+      if(!this.hoveredNode && !node) return;
+      //if the node is hovered then exit
+      if(node.hovered) return;
+      //select hovered node
+      if(node && !node.selected) {
+        //check if an animation is running and exit it
+        this.fx.nodeFxAnimation.stopTimer();
+        //unselect all hovered nodes...
+        this.viz.graph.eachNode(function(n) {
+          if(n.hovered && !n.selected) {
+            for(var s in nStyles) {
+              n.setData(s, n.styles['$' + s], 'end');
+            }
+            delete n.hovered;
+          }
+        });
+        //select hovered node
+        node.hovered = true;
+        this.hoveredNode = node;
+        this.toggleStylesOnHover(node, true);
+      } else if(this.hoveredNode && !this.hoveredNode.selected) {
+        //check if an animation is running and exit it
+        this.fx.nodeFxAnimation.stopTimer();
+        //unselect hovered node
+        this.toggleStylesOnHover(this.hoveredNode, false);
+        delete this.hoveredNode.hovered;
+        this.hoveredNode = false;
+      }
+    }
+  }
+});
+
+MultiExtras.Classes.Navigation = new Class({
+  Implements: [MultiExtrasInitializer, MultiEventsInterface],
+  
+  initializePost: function() {
+    this.pos = false;
+    this.pressed = false;
+  },
+  
+  onMouseWheel: function(e, win, scroll) {
+    if(!this.config.zooming) return;
+    $.event.stop($.event.get(e, win));
+    var val = this.config.zooming / 1000,
+        ans = 1 + scroll * val;
+    this.canvas.scale(ans, ans);
+  },
+  
+  onMouseDown: function(e, win, eventInfo) {
+    if(!this.config.panning) return;
+    e.preventDefault ? e.preventDefault() : e.returnValue = false;
+    $.addClass(this.canvas.getElement(), 'grabbing');
+    if(this.config.panning == 'avoid nodes' && (this.dom? this.isLabel(e, win) : eventInfo.getNode())) return;
+    this.pressed = true;
+    this.pos = eventInfo.getPos();
+    var canvas = this.canvas,
+        ox = canvas.translateOffsetX,
+        oy = canvas.translateOffsetY,
+        sx = canvas.scaleOffsetX,
+        sy = canvas.scaleOffsetY;
+    this.pos.x *= sx;
+    this.pos.x += ox;
+    this.pos.y *= sy;
+    this.pos.y += oy;
+  },
+  
+  onMouseMove: function(e, win, eventInfo) {
+    if(!this.config.panning) return;
+    if(!this.pressed) return;
+    if(this.config.panning == 'avoid nodes' && (this.dom? this.isLabel(e, win) : eventInfo.getNode())) return;
+    var thispos = this.pos, 
+        currentPos = eventInfo.getPos(),
+        canvas = this.canvas,
+        ox = canvas.translateOffsetX,
+        oy = canvas.translateOffsetY,
+        sx = canvas.scaleOffsetX,
+        sy = canvas.scaleOffsetY;
+    currentPos.x *= sx;
+    currentPos.y *= sy;
+    currentPos.x += ox;
+    currentPos.y += oy;
+    var x = currentPos.x - thispos.x,
+        y = currentPos.y - thispos.y;
+    this.pos = currentPos;
+    this.canvas.translate(x * 1/sx, y * 1/sy);
+  },
+  
+  onMouseUp: function(e, win, eventInfo, isRightClick) {
+    if(!this.config.panning) return;
+    $.removeClass(this.canvas.getElement(), 'grabbing');
+    this.pressed = false;
+  }
+});
+
+
+/*
+ * File: Canvas.js
+ *
+ */
+
+/*
+ Class: Canvas
+       A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
+       know more about <Canvas> options take a look at <Options.Canvas>.
+ A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
+ across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
+ Example:
+ Suppose we have this HTML
+ (start code xml)
+       <div id="infovis"></div>
+ (end code)
+ Now we create a new Visualization
+ (start code js)
+       var viz = new $jit.Viz({
+               //Where to inject the canvas. Any div container will do.
+               'injectInto':'infovis',
+                //width and height for canvas. 
+                //Default's to the container offsetWidth and Height.
+                'width': 900,
+                'height':500
+        });
+ (end code)
+
+ The generated HTML will look like this
+ (start code xml)
+ <div id="infovis">
+       <div id="infovis-canvaswidget" style="position:relative;">
+       <canvas id="infovis-canvas" width=900 height=500
+       style="position:absolute; top:0; left:0; width:900px; height:500px;" />
+       <div id="infovis-label"
+       style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
+       </div>
+       </div>
+ </div>
+ (end code)
+ As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
+ of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
+ */
+
+var Canvas;
+(function() {
+  //check for native canvas support
+  var canvasType = typeof HTMLCanvasElement,
+      supportsCanvas = (canvasType == 'object' || canvasType == 'function');
+  //create element function
+  function $E(tag, props) {
+    var elem = document.createElement(tag);
+    for(var p in props) {
+      if(typeof props[p] == "object") {
+        $.extend(elem[p], props[p]);
+      } else {
+        elem[p] = props[p];
+      }
+    }
+    if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
+      elem = G_vmlCanvasManager.initElement(document.body.appendChild(elem));
+    }
+    return elem;
+  }
+  //canvas widget which we will call just Canvas
+  $jit.Canvas = Canvas = new Class({
+    canvases: [],
+    pos: false,
+    element: false,
+    labelContainer: false,
+    translateOffsetX: 0,
+    translateOffsetY: 0,
+    scaleOffsetX: 1,
+    scaleOffsetY: 1,
+    
+    initialize: function(viz, opt) {
+      this.viz = viz;
+      this.opt = this.config = opt;
+      var id = $.type(opt.injectInto) == 'string'? 
+          opt.injectInto:opt.injectInto.id,
+          type = opt.type,
+          idLabel = id + "-label", 
+          wrapper = $(id),
+          width = opt.width || wrapper.offsetWidth,
+          height = opt.height || wrapper.offsetHeight;
+      this.id = id;
+      //canvas options
+      var canvasOptions = {
+        injectInto: id,
+        width: width,
+        height: height
+      };
+      //create main wrapper
+      this.element = $E('div', {
+        'id': id + '-canvaswidget',
+        'style': {
+          'position': 'relative',
+          'width': width + 'px',
+          'height': height + 'px'
+        }
+      });
+      //create label container
+      this.labelContainer = this.createLabelContainer(opt.Label.type, 
+          idLabel, canvasOptions);
+      //create primary canvas
+      this.canvases.push(new Canvas.Base[type]({
+        config: $.extend({idSuffix: '-canvas'}, canvasOptions),
+        plot: function(base) {
+          viz.fx.plot();
+        },
+        resize: function() {
+          viz.refresh();
+        }
+      }));
+      //create secondary canvas
+      var back = opt.background;
+      if(back) {
+        var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
+        this.canvases.push(new Canvas.Base[type](backCanvas));
+      }
+      //insert canvases
+      var len = this.canvases.length;
+      while(len--) {
+        this.element.appendChild(this.canvases[len].canvas);
+        if(len > 0) {
+          this.canvases[len].plot();
+        }
+      }
+      this.element.appendChild(this.labelContainer);
+      wrapper.appendChild(this.element);
+      //Update canvas position when the page is scrolled.
+      var timer = null, that = this;
+      $.addEvent(window, 'scroll', function() {
+        clearTimeout(timer);
+        timer = setTimeout(function() {
+          that.getPos(true); //update canvas position
+        }, 500);
+      });
+    },
+    /*
+      Method: getCtx
+      
+      Returns the main canvas context object
+      
+      Example:
+      
+      (start code js)
+       var ctx = canvas.getCtx();
+       //Now I can use the native canvas context
+       //and for example change some canvas styles
+       ctx.globalAlpha = 1;
+      (end code)
+    */
+    getCtx: function(i) {
+      return this.canvases[i || 0].getCtx();
+    },
+    /*
+      Method: getConfig
+      
+      Returns the current Configuration for this Canvas Widget.
+      
+      Example:
+      
+      (start code js)
+       var config = canvas.getConfig();
+      (end code)
+    */
+    getConfig: function() {
+      return this.opt;
+    },
+    /*
+      Method: getElement
+
+      Returns the main Canvas DOM wrapper
+      
+      Example:
+      
+      (start code js)
+       var wrapper = canvas.getElement();
+       //Returns <div id="infovis-canvaswidget" ... >...</div> as element
+      (end code)
+    */
+    getElement: function() {
+      return this.element;
+    },
+    /*
+      Method: getSize
+      
+      Returns canvas dimensions.
+      
+      Returns:
+      
+      An object with *width* and *height* properties.
+      
+      Example:
+      (start code js)
+      canvas.getSize(); //returns { width: 900, height: 500 }
+      (end code)
+    */
+    getSize: function(i) {
+      return this.canvases[i || 0].getSize();
+    },
+    /*
+      Method: resize
+      
+      Resizes the canvas.
+      
+      Parameters:
+      
+      width - New canvas width.
+      height - New canvas height.
+      
+      Example:
+      
+      (start code js)
+       canvas.resize(width, height);
+      (end code)
+    
+    */
+    resize: function(width, height) {
+      this.getPos(true);
+      this.translateOffsetX = this.translateOffsetY = 0;
+      this.scaleOffsetX = this.scaleOffsetY = 1;
+      for(var i=0, l=this.canvases.length; i<l; i++) {
+        this.canvases[i].resize(width, height);
+      }
+      var style = this.element.style;
+      style.width = width + 'px';
+      style.height = height + 'px';
+      if(this.labelContainer)
+        this.labelContainer.style.width = width + 'px';
+    },
+    /*
+      Method: translate
+      
+      Applies a translation to the canvas.
+      
+      Parameters:
+      
+      x - (number) x offset.
+      y - (number) y offset.
+      disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
+      
+      Example:
+      
+      (start code js)
+       canvas.translate(30, 30);
+      (end code)
+    
+    */
+    translate: function(x, y, disablePlot) {
+      this.translateOffsetX += x*this.scaleOffsetX;
+      this.translateOffsetY += y*this.scaleOffsetY;
+      for(var i=0, l=this.canvases.length; i<l; i++) {
+        this.canvases[i].translate(x, y, disablePlot);
+      }
+    },
+    /*
+      Method: scale
+      
+      Scales the canvas.
+      
+      Parameters:
+      
+      x - (number) scale value.
+      y - (number) scale value.
+      disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
+      
+      Example:
+      
+      (start code js)
+       canvas.scale(0.5, 0.5);
+      (end code)
+    
+    */
+    scale: function(x, y, disablePlot) {
+      var px = this.scaleOffsetX * x,
+          py = this.scaleOffsetY * y;
+      var dx = this.translateOffsetX * (x -1) / px,
+          dy = this.translateOffsetY * (y -1) / py;
+      this.scaleOffsetX = px;
+      this.scaleOffsetY = py;
+      for(var i=0, l=this.canvases.length; i<l; i++) {
+        this.canvases[i].scale(x, y, true);
+      }
+      this.translate(dx, dy, false);
+    },
+    /*
+      Method: getZoom
+
+      Returns canvas zooming factors. *1* means initial zoom.
+
+      Returns:
+
+      An object with *x* and *y* properties.
+    */
+    getZoom: function() {
+      return new Complex(this.scaleOffsetX, this.scaleOffsetY);
+    },
+    /*
+      Method: setZoom
+
+      Sets the zoom to given factors. *1* means initial zoom.
+
+      Parameters:
+
+      x - (number) zooming factor
+      y - (number) zooming factor
+      disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
+
+      Example:
+      (start code js)
+      canvas.setZoom(2, 2); //sets 2x zoom
+      (end code)
+    */
+    setZoom: function(x, y, disablePlot) {
+      var cur = this.getZoom(),
+          px = x / cur.x,
+          py = y / cur.y;
+      this.scale(px, py, disablePlot);
+    },
+    /*
+      Method: getPos
+      
+      Returns the canvas position as an *x, y* object.
+      
+      Parameters:
+      
+      force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
+      
+      Returns:
+      
+      An object with *x* and *y* properties.
+      
+      Example:
+      (start code js)
+      canvas.getPos(true); //returns { x: 900, y: 500 }
+      (end code)
+    */
+    getPos: function(force){
+      if(force || !this.pos) {
+        return this.pos = $.getPos(this.getElement());
+      }
+      return this.pos;
+    },
+    /*
+       Method: clear
+       
+       Clears the canvas.
+    */
+    clear: function(i){
+      this.canvases[i||0].clear();
+    },
+    
+    path: function(type, action){
+      var ctx = this.canvases[0].getCtx();
+      ctx.beginPath();
+      action(ctx);
+      ctx[type]();
+      ctx.closePath();
+    },
+    
+    createLabelContainer: function(type, idLabel, dim) {
+      var NS = 'http://www.w3.org/2000/svg';
+      if(type == 'HTML' || type == 'Native') {
+        return $E('div', {
+          'id': idLabel,
+          'style': {
+            'overflow': 'visible',
+            'position': 'absolute',
+            'top': 0,
+            'left': 0,
+            'width': dim.width + 'px',
+            'height': 0
+          }
+        });
+      } else if(type == 'SVG') {
+        var svgContainer = document.createElementNS(NS, 'svg:svg');
+        svgContainer.setAttribute("width", dim.width);
+        svgContainer.setAttribute('height', dim.height);
+        var style = svgContainer.style;
+        style.position = 'absolute';
+        style.left = style.top = '0px';
+        var labelContainer = document.createElementNS(NS, 'svg:g');
+        labelContainer.setAttribute('width', dim.width);
+        labelContainer.setAttribute('height', dim.height);
+        labelContainer.setAttribute('x', 0);
+        labelContainer.setAttribute('y', 0);
+        labelContainer.setAttribute('id', idLabel);
+        svgContainer.appendChild(labelContainer);
+        return svgContainer;
+      }
+    }
+  });
+  //base canvas wrapper
+  Canvas.Base = {};
+  Canvas.Base['2D'] = new Class({
+    translateOffsetX: 0,
+    translateOffsetY: 0,
+    scaleOffsetX: 1,
+    scaleOffsetY: 1,
+
+    initialize: function(viz) {
+      this.viz = viz;
+      this.opt = viz.config;
+      this.size = false;
+      this.createCanvas();
+      this.translateToCenter();
+    },
+    createCanvas: function() {
+      var opt = this.opt,
+          width = opt.width,
+          height = opt.height;
+      this.canvas = $E('canvas', {
+        'id': opt.injectInto + opt.idSuffix,
+        'width': width,
+        'height': height,
+        'style': {
+          'position': 'absolute',
+          'top': 0,
+          'left': 0,
+          'width': width + 'px',
+          'height': height + 'px'
+        }
+      });
+    },
+    getCtx: function() {
+      if(!this.ctx) 
+        return this.ctx = this.canvas.getContext('2d');
+      return this.ctx;
+    },
+    getSize: function() {
+      if(this.size) return this.size;
+      var canvas = this.canvas;
+      return this.size = {
+        width: canvas.width,
+        height: canvas.height
+      };
+    },
+    translateToCenter: function(ps) {
+      var size = this.getSize(),
+          width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
+          height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
+      var ctx = this.getCtx();
+      ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
+      ctx.translate(width/2, height/2);
+    },
+    resize: function(width, height) {
+      var size = this.getSize(),
+          canvas = this.canvas,
+          styles = canvas.style;
+      this.size = false;
+      canvas.width = width;
+      canvas.height = height;
+      styles.width = width + "px";
+      styles.height = height + "px";
+      //small ExCanvas fix
+      if(!supportsCanvas) {
+        this.translateToCenter(size);
+      } else {
+        this.translateToCenter();
+      }
+      this.translateOffsetX =
+        this.translateOffsetY = 0;
+      this.scaleOffsetX = 
+        this.scaleOffsetY = 1;
+      this.clear();
+      this.viz.resize(width, height, this);
+    },
+    translate: function(x, y, disablePlot) {
+      var sx = this.scaleOffsetX,
+          sy = this.scaleOffsetY;
+      this.translateOffsetX += x*sx;
+      this.translateOffsetY += y*sy;
+      this.getCtx().translate(x, y);
+      !disablePlot && this.plot();
+    },
+    scale: function(x, y, disablePlot) {
+      this.scaleOffsetX *= x;
+      this.scaleOffsetY *= y;
+      this.getCtx().scale(x, y);
+      !disablePlot && this.plot();
+    },
+    clear: function(){
+      var size = this.getSize(),
+          ox = this.translateOffsetX,
+          oy = this.translateOffsetY,
+          sx = this.scaleOffsetX,
+          sy = this.scaleOffsetY;
+      this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
+                              (-size.height / 2 - oy) * 1/sy, 
+                              size.width * 1/sx, size.height * 1/sy);
+    },
+    plot: function() {
+      this.clear();
+      this.viz.plot(this);
+    }
+  });
+  //background canvases
+  //TODO(nico): document this!
+  Canvas.Background = {};
+  Canvas.Background.Circles = new Class({
+    initialize: function(viz, options) {
+      this.viz = viz;
+      this.config = $.merge({
+        idSuffix: '-bkcanvas',
+        levelDistance: 100,
+        numberOfCircles: 6,
+        CanvasStyles: {},
+        offset: 0
+      }, options);
+    },
+    resize: function(width, height, base) {
+      this.plot(base);
+    },
+    plot: function(base) {
+      var canvas = base.canvas,
+          ctx = base.getCtx(),
+          conf = this.config,
+          styles = conf.CanvasStyles;
+      //set canvas styles
+      for(var s in styles) ctx[s] = styles[s];
+      var n = conf.numberOfCircles,
+          rho = conf.levelDistance;
+      for(var i=1; i<=n; i++) {
+        ctx.beginPath();
+        ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
+        ctx.stroke();
+        ctx.closePath();
+      }
+      //TODO(nico): print labels too!
+    }
+  });
+})();
+
+
+/*
+ * File: Polar.js
+ * 
+ * Defines the <Polar> class.
+ *
+ * Description:
+ *
+ * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+ *
+ * See also:
+ *
+ * <http://en.wikipedia.org/wiki/Polar_coordinates>
+ *
+*/
+
+/*
+   Class: Polar
+
+   A multi purpose polar representation.
+
+   Description:
+   The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+   See also:
+   <http://en.wikipedia.org/wiki/Polar_coordinates>
+   Parameters:
+
+      theta - An angle.
+      rho - The norm.
+*/
+
+var Polar = function(theta, rho) {
+  this.theta = theta || 0;
+  this.rho = rho || 0;
+};
+
+$jit.Polar = Polar;
+
+Polar.prototype = {
+    /*
+       Method: getc
+    
+       Returns a complex number.
+    
+       Parameters:
+
+       simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
+
+      Returns:
+    
+          A complex number.
+    */
+    getc: function(simple) {
+        return this.toComplex(simple);
+    },
+
+    /*
+       Method: getp
+    
+       Returns a <Polar> representation.
+    
+       Returns:
+    
+          A variable in polar coordinates.
+    */
+    getp: function() {
+        return this;
+    },
+
+
+    /*
+       Method: set
+    
+       Sets a number.
+
+       Parameters:
+
+       v - A <Complex> or <Polar> instance.
+    
+    */
+    set: function(v) {
+        v = v.getp();
+        this.theta = v.theta; this.rho = v.rho;
+    },
+
+    /*
+       Method: setc
+    
+       Sets a <Complex> number.
+
+       Parameters:
+
+       x - A <Complex> number real part.
+       y - A <Complex> number imaginary part.
+    
+    */
+    setc: function(x, y) {
+        this.rho = Math.sqrt(x * x + y * y);
+        this.theta = Math.atan2(y, x);
+        if(this.theta < 0) this.theta += Math.PI * 2;
+    },
+
+    /*
+       Method: setp
+    
+       Sets a polar number.
+
+       Parameters:
+
+       theta - A <Polar> number angle property.
+       rho - A <Polar> number rho property.
+    
+    */
+    setp: function(theta, rho) {
+        this.theta = theta; 
+        this.rho = rho;
+    },
+
+    /*
+       Method: clone
+    
+       Returns a copy of the current object.
+    
+       Returns:
+    
+          A copy of the real object.
+    */
+    clone: function() {
+        return new Polar(this.theta, this.rho);
+    },
+
+    /*
+       Method: toComplex
+    
+        Translates from polar to cartesian coordinates and returns a new <Complex> instance.
+    
+        Parameters:
+
+        simple - _optional_ If *true* this method will only return an object with x and y properties (and not the whole <Complex> instance). Default's *false*.
+        Returns:
+    
+          A new <Complex> instance.
+    */
+    toComplex: function(simple) {
+        var x = Math.cos(this.theta) * this.rho;
+        var y = Math.sin(this.theta) * this.rho;
+        if(simple) return { 'x': x, 'y': y};
+        return new Complex(x, y);
+    },
+
+    /*
+       Method: add
+    
+        Adds two <Polar> instances.
+    
+       Parameters:
+
+       polar - A <Polar> number.
+
+       Returns:
+    
+          A new Polar instance.
+    */
+    add: function(polar) {
+        return new Polar(this.theta + polar.theta, this.rho + polar.rho);
+    },
+    
+    /*
+       Method: scale
+    
+        Scales a polar norm.
+    
+        Parameters:
+
+        number - A scale factor.
+        
+        Returns:
+    
+          A new Polar instance.
+    */
+    scale: function(number) {
+        return new Polar(this.theta, this.rho * number);
+    },
+    
+    /*
+       Method: equals
+    
+       Comparison method.
+
+       Returns *true* if the theta and rho properties are equal.
+
+       Parameters:
+
+       c - A <Polar> number.
+
+       Returns:
+
+       *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
+    */
+    equals: function(c) {
+        return this.theta == c.theta && this.rho == c.rho;
+    },
+    
+    /*
+       Method: $add
+    
+        Adds two <Polar> instances affecting the current object.
+    
+       Paramters:
+
+       polar - A <Polar> instance.
+
+       Returns:
+    
+          The changed object.
+    */
+    $add: function(polar) {
+        this.theta = this.theta + polar.theta; this.rho += polar.rho;
+        return this;
+    },
+
+    /*
+       Method: $madd
+    
+        Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
+    
+       Parameters:
+
+       polar - A <Polar> instance.
+
+       Returns:
+    
+          The changed object.
+    */
+    $madd: function(polar) {
+        this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
+        return this;
+    },
+
+    
+    /*
+       Method: $scale
+    
+        Scales a polar instance affecting the object.
+    
+      Parameters:
+
+      number - A scaling factor.
+
+      Returns:
+    
+          The changed object.
+    */
+    $scale: function(number) {
+        this.rho *= number;
+        return this;
+    },
+    
+    /*
+      Method: isZero
+   
+      Returns *true* if the number is zero.
+   
+   */
+    isZero: function () {
+      var almostZero = 0.0001, abs = Math.abs;
+      return abs(this.theta) < almostZero && abs(this.rho) < almostZero;
+    },
+
+    /*
+       Method: interpolate
+    
+        Calculates a polar interpolation between two points at a given delta moment.
+
+        Parameters:
+      
+        elem - A <Polar> instance.
+        delta - A delta factor ranging [0, 1].
+    
+       Returns:
+    
+          A new <Polar> instance representing an interpolation between _this_ and _elem_
+    */
+    interpolate: function(elem, delta) {
+        var pi = Math.PI, pi2 = pi * 2;
+        var ch = function(t) {
+            var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
+            return a;
+        };
+        var tt = this.theta, et = elem.theta;
+        var sum, diff = Math.abs(tt - et);
+        if(diff == pi) {
+          if(tt > et) {
+            sum = ch((et + ((tt - pi2) - et) * delta)) ;
+          } else {
+            sum = ch((et - pi2 + (tt - (et)) * delta));
+          }
+        } else if(diff >= pi) {
+          if(tt > et) {
+            sum = ch((et + ((tt - pi2) - et) * delta)) ;
+          } else {
+            sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
+          }
+        } else {  
+          sum = ch((et + (tt - et) * delta)) ;
+        }
+        var r = (this.rho - elem.rho) * delta + elem.rho;
+        return {
+          'theta': sum,
+          'rho': r
+        };
+    }
+};
+
+
+var $P = function(a, b) { return new Polar(a, b); };
+
+Polar.KER = $P(0, 0);
+
+
+
+/*
+ * File: Complex.js
+ * 
+ * Defines the <Complex> class.
+ *
+ * Description:
+ *
+ * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+ *
+ * See also:
+ *
+ * <http://en.wikipedia.org/wiki/Complex_number>
+ *
+*/
+
+/*
+   Class: Complex
+    
+   A multi-purpose Complex Class with common methods.
+   Description:
+   The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+   See also:
+   <http://en.wikipedia.org/wiki/Complex_number>
+
+   Parameters:
+
+   x - _optional_ A Complex number real part.
+   y - _optional_ A Complex number imaginary part.
+*/
+
+var Complex = function(x, y) {
+  this.x = x || 0;
+  this.y = y || 0;
+};
+
+$jit.Complex = Complex;
+
+Complex.prototype = {
+    /*
+       Method: getc
+    
+       Returns a complex number.
+    
+       Returns:
+    
+          A complex number.
+    */
+    getc: function() {
+        return this;
+    },
+
+    /*
+       Method: getp
+    
+       Returns a <Polar> representation of this number.
+    
+       Parameters:
+
+       simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
+
+       Returns:
+    
+          A variable in <Polar> coordinates.
+    */
+    getp: function(simple) {
+        return this.toPolar(simple);
+    },
+
+
+    /*
+       Method: set
+    
+       Sets a number.
+
+       Parameters:
+
+       c - A <Complex> or <Polar> instance.
+    
+    */
+    set: function(c) {
+      c = c.getc(true);
+      this.x = c.x; 
+      this.y = c.y;
+    },
+
+    /*
+       Method: setc
+    
+       Sets a complex number.
+
+       Parameters:
+
+       x - A <Complex> number Real part.
+       y - A <Complex> number Imaginary part.
+    
+    */
+    setc: function(x, y) {
+        this.x = x; 
+        this.y = y;
+    },
+
+    /*
+       Method: setp
+    
+       Sets a polar number.
+
+       Parameters:
+
+       theta - A <Polar> number theta property.
+       rho - A <Polar> number rho property.
+    
+    */
+    setp: function(theta, rho) {
+        this.x = Math.cos(theta) * rho;
+        this.y = Math.sin(theta) * rho;
+    },
+
+    /*
+       Method: clone
+    
+       Returns a copy of the current object.
+    
+       Returns:
+    
+          A copy of the real object.
+    */
+    clone: function() {
+        return new Complex(this.x, this.y);
+    },
+
+    /*
+       Method: toPolar
+    
+       Transforms cartesian to polar coordinates.
+    
+       Parameters:
+
+       simple - _optional_ If *true* this method will only return an object with theta and rho properties (and not the whole <Polar> instance). Default's *false*.
+       
+       Returns:
+    
+          A new <Polar> instance.
+    */
+    
+    toPolar: function(simple) {
+        var rho = this.norm();
+        var atan = Math.atan2(this.y, this.x);
+        if(atan < 0) atan += Math.PI * 2;
+        if(simple) return { 'theta': atan, 'rho': rho };
+        return new Polar(atan, rho);
+    },
+    /*
+       Method: norm
+    
+       Calculates a <Complex> number norm.
+    
+       Returns:
+    
+          A real number representing the complex norm.
+    */
+    norm: function () {
+        return Math.sqrt(this.squaredNorm());
+    },
+    
+    /*
+       Method: squaredNorm
+    
+       Calculates a <Complex> number squared norm.
+    
+       Returns:
+    
+          A real number representing the complex squared norm.
+    */
+    squaredNorm: function () {
+        return this.x*this.x + this.y*this.y;
+    },
+
+    /*
+       Method: add
+    
+       Returns the result of adding two complex numbers.
+       
+       Does not alter the original object.
+
+       Parameters:
+    
+          pos - A <Complex> instance.
+    
+       Returns:
+    
+         The result of adding two complex numbers.
+    */
+    add: function(pos) {
+        return new Complex(this.x + pos.x, this.y + pos.y);
+    },
+
+    /*
+       Method: prod
+    
+       Returns the result of multiplying two <Complex> numbers.
+       
+       Does not alter the original object.
+
+       Parameters:
+    
+          pos - A <Complex> instance.
+    
+       Returns:
+    
+         The result of multiplying two complex numbers.
+    */
+    prod: function(pos) {
+        return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
+    },
+
+    /*
+       Method: conjugate
+    
+       Returns the conjugate of this <Complex> number.
+
+       Does not alter the original object.
+
+       Returns:
+    
+         The conjugate of this <Complex> number.
+    */
+    conjugate: function() {
+        return new Complex(this.x, -this.y);
+    },
+
+
+    /*
+       Method: scale
+    
+       Returns the result of scaling a <Complex> instance.
+       
+       Does not alter the original object.
+
+       Parameters:
+    
+          factor - A scale factor.
+    
+       Returns:
+    
+         The result of scaling this complex to a factor.
+    */
+    scale: function(factor) {
+        return new Complex(this.x * factor, this.y * factor);
+    },
+
+    /*
+       Method: equals
+    
+       Comparison method.
+
+       Returns *true* if both real and imaginary parts are equal.
+
+       Parameters:
+
+       c - A <Complex> instance.
+
+       Returns:
+
+       A boolean instance indicating if both <Complex> numbers are equal.
+    */
+    equals: function(c) {
+        return this.x == c.x && this.y == c.y;
+    },
+
+    /*
+       Method: $add
+    
+       Returns the result of adding two <Complex> numbers.
+       
+       Alters the original object.
+
+       Parameters:
+    
+          pos - A <Complex> instance.
+    
+       Returns:
+    
+         The result of adding two complex numbers.
+    */
+    $add: function(pos) {
+        this.x += pos.x; this.y += pos.y;
+        return this;    
+    },
+    
+    /*
+       Method: $prod
+    
+       Returns the result of multiplying two <Complex> numbers.
+       
+       Alters the original object.
+
+       Parameters:
+    
+          pos - A <Complex> instance.
+    
+       Returns:
+    
+         The result of multiplying two complex numbers.
+    */
+    $prod:function(pos) {
+        var x = this.x, y = this.y;
+        this.x = x*pos.x - y*pos.y;
+        this.y = y*pos.x + x*pos.y;
+        return this;
+    },
+    
+    /*
+       Method: $conjugate
+    
+       Returns the conjugate for this <Complex>.
+       
+       Alters the original object.
+
+       Returns:
+    
+         The conjugate for this complex.
+    */
+    $conjugate: function() {
+        this.y = -this.y;
+        return this;
+    },
+    
+    /*
+       Method: $scale
+    
+       Returns the result of scaling a <Complex> instance.
+       
+       Alters the original object.
+
+       Parameters:
+    
+          factor - A scale factor.
+    
+       Returns:
+    
+         The result of scaling this complex to a factor.
+    */
+    $scale: function(factor) {
+        this.x *= factor; this.y *= factor;
+        return this;
+    },
+    
+    /*
+       Method: $div
+    
+       Returns the division of two <Complex> numbers.
+       
+       Alters the original object.
+
+       Parameters:
+    
+          pos - A <Complex> number.
+    
+       Returns:
+    
+         The result of scaling this complex to a factor.
+    */
+    $div: function(pos) {
+        var x = this.x, y = this.y;
+        var sq = pos.squaredNorm();
+        this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
+        return this.$scale(1 / sq);
+    },
+
+    /*
+      Method: isZero
+   
+      Returns *true* if the number is zero.
+   
+   */
+    isZero: function () {
+      var almostZero = 0.0001, abs = Math.abs;
+      return abs(this.x) < almostZero && abs(this.y) < almostZero;
+    }
+};
+
+var $C = function(a, b) { return new Complex(a, b); };
+
+Complex.KER = $C(0, 0);
+Complex.IM = $C(0, 1);
+
+
+
+/*
+ * File: Graph.js
+ *
+*/
+
+/*
+ Class: Graph
+
+ A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
+
+ An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
+ Example:
+
+ (start code js)
+   //create new visualization
+   var viz = new $jit.Viz(options);
+   //load JSON data
+   viz.loadJSON(json);
+   //access model
+   viz.graph; //<Graph> instance
+ (end code)
+ Implements:
+ The following <Graph.Util> methods are implemented in <Graph>
+  - <Graph.Util.getNode>
+  - <Graph.Util.eachNode>
+  - <Graph.Util.computeLevels>
+  - <Graph.Util.eachBFS>
+  - <Graph.Util.clean>
+  - <Graph.Util.getClosestNodeToPos>
+  - <Graph.Util.getClosestNodeToOrigin>
+*/  
+
+$jit.MultiGraph = new Class({
+
+  initialize: function(opt, Node, Edge, Label) {
+    var innerOptions = {
+    'klass': Complex,
+    'Node': {}
+    };
+    this.Node = Node;
+    this.Edge = Edge;
+    this.Label = Label;
+    this.opt = $.merge(innerOptions, opt || {});
+    this.nodes = {};
+    this.edges = {};
+       this.dup = []; // duplicate (bidirectional) edges
+    
+    //add nodeList methods
+    var that = this;
+    this.nodeList = {};
+    for(var p in Accessors) {
+      that.nodeList[p] = (function(p) {
+        return function() {
+          var args = Array.prototype.slice.call(arguments);
+          that.eachNode(function(n) {
+            n[p].apply(n, args);
+          });
+        };
+      })(p);
+    }
+
+ },
+
+/*
+     Method: getNode
+    
+     Returns a <Graph.Node> by *id*.
+
+     Parameters:
+
+     id - (string) A <Graph.Node> id.
+
+     Example:
+
+     (start code js)
+       var node = graph.getNode('nodeId');
+     (end code)
+*/  
+ getNode: function(id) {
+    if(this.hasNode(id)) return this.nodes[id];
+    return false;
+ },
+
+ /*
+     Method: get
+    
+     An alias for <Graph.Util.getNode>. Returns a node by *id*.
+    
+     Parameters:
+    
+     id - (string) A <Graph.Node> id.
+    
+     Example:
+    
+     (start code js)
+       var node = graph.get('nodeId');
+     (end code)
+*/  
+  get: function(id) {
+    return this.getNode(id);
+  },
+
+ /*
+   Method: getByName
+  
+   Returns a <Graph.Node> by *name*.
+  
+   Parameters:
+  
+   name - (string) A <Graph.Node> name.
+  
+   Example:
+  
+   (start code js)
+     var node = graph.getByName('someName');
+   (end code)
+  */  
+  getByName: function(name) {
+    for(var id in this.nodes) {
+      var n = this.nodes[id];
+      if(n.name == name) return n;
+    }
+    return false;
+  },
+
+/*
+   Method: getAdjacence
+  
+   Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
+
+   Parameters:
+
+   id - (string) A <Graph.Node> id.
+   id2 - (string) A <Graph.Node> id.
+*/  
+  getAdjacence: function (id, id2) {
+    if(id in this.edges) {
+      return this.edges[id][id2];
+    }
+    return false;
+ },
+
+    /*
+     Method: addNode
+    
+     Adds a node.
+     
+     Parameters:
+    
+      obj - An object with the properties described below
+
+      id - (string) A node id
+      name - (string) A node's name
+      data - (object) A node's data hash
+
+    See also:
+    <Graph.Node>
+
+  */  
+  addNode: function(obj) { 
+   if(!this.nodes[obj.id]) {  
+     var edges = this.edges[obj.id] = {};
+     this.nodes[obj.id] = new MultiGraph.Node($.extend({
+        'id': obj.id,
+        'name': obj.name,
+        'data': $.merge(obj.data || {}, {}),
+        'adjacencies': edges 
+      }, this.opt.Node), 
+      this.opt.klass, 
+      this.Node, 
+      this.Edge,
+      this.Label);
+    }
+    return this.nodes[obj.id];
+  },
+  
+    /*
+     Method: addAdjacence
+    
+     Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
+     
+     Parameters:
+    
+      obj - (object) A <Graph.Node> object.
+      obj2 - (object) Another <Graph.Node> object.
+      data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
+
+    See also:
+
+    <Graph.Node>, <Graph.Adjacence>
+    */  
+  addAdjacence: function (obj, obj2, data) {
+       if (obj.id == obj2.id) return null; // skip if node loops back to itself
+
+       var portFrom = data["$nodeFromPort"];
+       var portTo = data["$nodeToPort"];
+
+    if(!this.hasNode(obj.id)) { this.addNode(obj); }
+    if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
+    obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
+    if(!obj.adjacentTo(obj2, portTo)) {
+      var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
+      var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
+         
+         adjsObj[obj2.id] = adjsObj[obj2.id] || [];
+         adjsObj2[obj.id] = adjsObj2[obj.id] || [];
+
+         var adj = new MultiGraph.Adjacence(obj, obj2, portFrom, portTo, data, this.Edge, this.Label);
+
+         // if dup, then add to dup list
+         for(var edge in adjsObj2[obj.id]) { // iterate through the adj for the other node
+               var e = adjsObj2[obj.id][edge];
+               if(e.nodeFrom.id == adj.nodeTo.id && e.nodeTo.id == adj.nodeFrom.id) {
+                       if(e.portFrom == adj.portTo && e.portTo == adj.portFrom) {
+                               this.dup.push(adj); // if the other node contains the edge, then add it to the dup list for later filtering
+                       }
+               }
+         }
+
+         // add this adjacency into the current edges object
+         adjsObj[obj2.id].push(adj);
+         if (adj.nodeFrom.data["$type"] != "swtch") {
+               // if this is not a switch (e.g. host), then add it to the switch because it will never be added by itself
+               adjsObj2[obj.id].push(adj);
+         }
+
+         return adj;
+      //adjsObj[obj2.id] = adjsObj2[obj.id] = new MultiGraph.Adjacence(obj, obj2, data["$nodeFromPort"], data["$nodeToPort"], data, this.Edge, this.Label);
+      //return adjsObj[obj2.id];
+    }
+    return this.edges[obj.id][obj2.id];
+ },
+
+    /*
+     Method: removeNode
+    
+     Removes a <Graph.Node> matching the specified *id*.
+
+     Parameters:
+
+     id - (string) A node's id.
+
+    */  
+  removeNode: function(id) {
+    if(this.hasNode(id)) {
+      delete this.nodes[id];
+      var adjs = this.edges[id];
+      for(var to in adjs) {
+        delete this.edges[to][id];
+      }
+      delete this.edges[id];
+    }
+  },
+  
+/*
+     Method: removeAdjacence
+    
+     Removes a <Graph.Adjacence> matching *id1* and *id2*.
+
+     Parameters:
+
+     id1 - (string) A <Graph.Node> id.
+     id2 - (string) A <Graph.Node> id.
+*/  
+  removeAdjacence: function(id1, id2) {
+    delete this.edges[id1][id2];
+    delete this.edges[id2][id1];
+  },
+
+   /*
+     Method: hasNode
+    
+     Returns a boolean indicating if the node belongs to the <Graph> or not.
+     
+     Parameters:
+    
+        id - (string) Node id.
+   */  
+  hasNode: function(id) {
+    return id in this.nodes;
+  },
+  
+  /*
+    Method: empty
+
+    Empties the Graph
+
+  */
+  empty: function() { this.nodes = {}; this.edges = {};}
+
+});
+
+var MultiGraph = $jit.MultiGraph;
+
+/*
+ Object: Accessors
+ Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
+ */
+var Accessors;
+
+(function () {
+  var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
+    var data;
+    type = type || 'current';
+    prefix = "$" + (prefix ? prefix + "-" : "");
+
+    if(type == 'current') {
+      data = this.data;
+    } else if(type == 'start') {
+      data = this.startData;
+    } else if(type == 'end') {
+      data = this.endData;
+    }
+
+    var dollar = prefix + prop;
+
+    if(force) {
+      return data[dollar];
+    }
+
+    if(!this.Config.overridable)
+      return prefixConfig[prop] || 0;
+
+    return (dollar in data) ?
+      data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
+  }
+
+  var setDataInternal = function(prefix, prop, value, type) {
+    type = type || 'current';
+    prefix = '$' + (prefix ? prefix + '-' : '');
+
+    var data;
+
+    if(type == 'current') {
+      data = this.data;
+    } else if(type == 'start') {
+      data = this.startData;
+    } else if(type == 'end') {
+      data = this.endData;
+    }
+
+    data[prefix + prop] = value;
+  }
+
+  var removeDataInternal = function(prefix, properties) {
+    prefix = '$' + (prefix ? prefix + '-' : '');
+    var that = this;
+    $.each(properties, function(prop) {
+      var pref = prefix + prop;
+      delete that.data[pref];
+      delete that.endData[pref];
+      delete that.startData[pref];
+    });
+  }
+
+  Accessors = {
+    /*
+    Method: getData
+
+    Returns the specified data value property.
+    This is useful for querying special/reserved <Graph.Node> data properties
+    (i.e dollar prefixed properties).
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign is not needed. For
+              example *getData(width)* will return *data.$width*.
+      type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
+              data properties also. These properties are used when making animations.
+      force - (boolean) Whether to obtain the true value of the property (equivalent to
+              *data.$prop*) or to check for *node.overridable = true* first.
+
+    Returns:
+
+      The value of the dollar prefixed property or the global Node/Edge property
+      value if *overridable=false*
+
+    Example:
+    (start code js)
+     node.getData('width'); //will return node.data.$width if Node.overridable=true;
+    (end code)
+    */
+    getData: function(prop, type, force) {
+      return getDataInternal.call(this, "", prop, type, force, this.Config);
+    },
+
+
+    /*
+    Method: setData
+
+    Sets the current data property with some specific value.
+    This method is only useful for reserved (dollar prefixed) properties.
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign is not necessary. For
+              example *setData(width)* will set *data.$width*.
+      value - (mixed) The value to store.
+      type  - (string) The type of the data property to store. Default's "current" but
+              can also be "start" or "end".
+
+    Example:
+    
+    (start code js)
+     node.setData('width', 30);
+    (end code)
+    
+    If we were to make an animation of a node/edge width then we could do
+    
+    (start code js)
+      var node = viz.getNode('nodeId');
+      //set start and end values
+      node.setData('width', 10, 'start');
+      node.setData('width', 30, 'end');
+      //will animate nodes width property
+      viz.fx.animate({
+        modes: ['node-property:width'],
+        duration: 1000
+      });
+    (end code)
+    */
+    setData: function(prop, value, type) {
+      setDataInternal.call(this, "", prop, value, type);
+    },
+
+    /*
+    Method: setDataset
+
+    Convenience method to set multiple data values at once.
+    
+    Parameters:
+    
+    types - (array|string) A set of 'current', 'end' or 'start' values.
+    obj - (object) A hash containing the names and values of the properties to be altered.
+
+    Example:
+    (start code js)
+      node.setDataset(['current', 'end'], {
+        'width': [100, 5],
+        'color': ['#fff', '#ccc']
+      });
+      //...or also
+      node.setDataset('end', {
+        'width': 5,
+        'color': '#ccc'
+      });
+    (end code)
+    
+    See also: 
+    
+    <Accessors.setData>
+    
+    */
+    setDataset: function(types, obj) {
+      types = $.splat(types);
+      for(var attr in obj) {
+        for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+          this.setData(attr, val[i], types[i]);
+        }
+      }
+    },
+    
+    /*
+    Method: removeData
+
+    Remove data properties.
+
+    Parameters:
+
+    One or more property names as arguments. The dollar sign is not needed.
+
+    Example:
+    (start code js)
+    node.removeData('width'); //now the default width value is returned
+    (end code)
+    */
+    removeData: function() {
+      removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
+    },
+
+    /*
+    Method: getCanvasStyle
+
+    Returns the specified canvas style data value property. This is useful for
+    querying special/reserved <Graph.Node> canvas style data properties (i.e.
+    dollar prefixed properties that match with $canvas-<name of canvas style>).
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign is not needed. For
+              example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
+      type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
+              data properties also.
+              
+    Example:
+    (start code js)
+      node.getCanvasStyle('shadowBlur');
+    (end code)
+    
+    See also:
+    
+    <Accessors.getData>
+    */
+    getCanvasStyle: function(prop, type, force) {
+      return getDataInternal.call(
+          this, 'canvas', prop, type, force, this.Config.CanvasStyles);
+    },
+
+    /*
+    Method: setCanvasStyle
+
+    Sets the canvas style data property with some specific value.
+    This method is only useful for reserved (dollar prefixed) properties.
+    
+    Parameters:
+    
+    prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
+    value - (mixed) The value to set to the property.
+    type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
+    
+    Example:
+    
+    (start code js)
+     node.setCanvasStyle('shadowBlur', 30);
+    (end code)
+    
+    If we were to make an animation of a node/edge shadowBlur canvas style then we could do
+    
+    (start code js)
+      var node = viz.getNode('nodeId');
+      //set start and end values
+      node.setCanvasStyle('shadowBlur', 10, 'start');
+      node.setCanvasStyle('shadowBlur', 30, 'end');
+      //will animate nodes canvas style property for nodes
+      viz.fx.animate({
+        modes: ['node-style:shadowBlur'],
+        duration: 1000
+      });
+    (end code)
+    
+    See also:
+    
+    <Accessors.setData>.
+    */
+    setCanvasStyle: function(prop, value, type) {
+      setDataInternal.call(this, 'canvas', prop, value, type);
+    },
+
+    /*
+    Method: setCanvasStyles
+
+    Convenience method to set multiple styles at once.
+
+    Parameters:
+    
+    types - (array|string) A set of 'current', 'end' or 'start' values.
+    obj - (object) A hash containing the names and values of the properties to be altered.
+
+    See also:
+    
+    <Accessors.setDataset>.
+    */
+    setCanvasStyles: function(types, obj) {
+      types = $.splat(types);
+      for(var attr in obj) {
+        for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+          this.setCanvasStyle(attr, val[i], types[i]);
+        }
+      }
+    },
+
+    /*
+    Method: removeCanvasStyle
+
+    Remove canvas style properties from data.
+
+    Parameters:
+    
+    A variable number of canvas style strings.
+
+    See also:
+    
+    <Accessors.removeData>.
+    */
+    removeCanvasStyle: function() {
+      removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
+    },
+
+    /*
+    Method: getLabelData
+
+    Returns the specified label data value property. This is useful for
+    querying special/reserved <Graph.Node> label options (i.e.
+    dollar prefixed properties that match with $label-<name of label style>).
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign prefix is not needed. For
+              example *getLabelData(size)* will return *data[$label-size]*.
+      type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
+              data properties also.
+              
+    See also:
+    
+    <Accessors.getData>.
+    */
+    getLabelData: function(prop, type, force) {
+      return getDataInternal.call(
+          this, 'label', prop, type, force, this.Label);
+    },
+
+    /*
+    Method: setLabelData
+
+    Sets the current label data with some specific value.
+    This method is only useful for reserved (dollar prefixed) properties.
+
+    Parameters:
+    
+    prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
+    value - (mixed) The value to set to the property.
+    type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
+    
+    Example:
+    
+    (start code js)
+     node.setLabelData('size', 30);
+    (end code)
+    
+    If we were to make an animation of a node label size then we could do
+    
+    (start code js)
+      var node = viz.getNode('nodeId');
+      //set start and end values
+      node.setLabelData('size', 10, 'start');
+      node.setLabelData('size', 30, 'end');
+      //will animate nodes label size
+      viz.fx.animate({
+        modes: ['label-property:size'],
+        duration: 1000
+      });
+    (end code)
+    
+    See also:
+    
+    <Accessors.setData>.
+    */
+    setLabelData: function(prop, value, type) {
+      setDataInternal.call(this, 'label', prop, value, type);
+    },
+
+    /*
+    Method: setLabelDataset
+
+    Convenience function to set multiple label data at once.
+
+    Parameters:
+    
+    types - (array|string) A set of 'current', 'end' or 'start' values.
+    obj - (object) A hash containing the names and values of the properties to be altered.
+
+    See also:
+    
+    <Accessors.setDataset>.
+    */
+    setLabelDataset: function(types, obj) {
+      types = $.splat(types);
+      for(var attr in obj) {
+        for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+          this.setLabelData(attr, val[i], types[i]);
+        }
+      }
+    },
+
+    /*
+    Method: removeLabelData
+
+    Remove label properties from data.
+    
+    Parameters:
+    
+    A variable number of label property strings.
+
+    See also:
+    
+    <Accessors.removeData>.
+    */
+    removeLabelData: function() {
+      removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
+    }
+  };
+})();
+
+/*
+     Class: Graph.Node
+
+     A <Graph> node.
+     
+     Implements:
+     
+     <Accessors> methods.
+     
+     The following <Graph.Util> methods are implemented by <Graph.Node>
+     
+    - <Graph.Util.eachAdjacency>
+    - <Graph.Util.eachLevel>
+    - <Graph.Util.eachSubgraph>
+    - <Graph.Util.eachSubnode>
+    - <Graph.Util.anySubnode>
+    - <Graph.Util.getSubnodes>
+    - <Graph.Util.getParents>
+    - <Graph.Util.isDescendantOf>     
+*/
+MultiGraph.Node = new Class({
+    
+  initialize: function(opt, klass, Node, Edge, Label) {
+    var innerOptions = {
+      'id': '',
+      'name': '',
+      'data': {},
+      'startData': {},
+      'endData': {},
+      'adjacencies': {},
+
+      'selected': false,
+      'drawn': false,
+      'exist': false,
+
+      'angleSpan': {
+        'begin': 0,
+        'end' : 0
+      },
+
+      'pos': new klass,
+      'startPos': new klass,
+      'endPos': new klass
+    };
+    
+    $.extend(this, $.extend(innerOptions, opt));
+    this.Config = this.Node = Node;
+    this.Edge = Edge;
+    this.Label = Label;
+  },
+
+    /*
+       Method: adjacentTo
+    
+       Indicates if the node is adjacent to the node specified by id
+
+       Parameters:
+    
+          id - (string) A node id.
+    
+       Example:
+       (start code js)
+        node.adjacentTo('nodeId') == true;
+       (end code)
+    */
+    adjacentTo: function(node, port) {
+               return false;
+               /*
+               if (this.adjacencies[node.id] != undefined) {
+                       for(var i = 0, il = this.adjacencies[node.id].length; i < il; i++) {
+                               if (this.adjacencies[node.id][i]["portFrom"] == port)
+                                       return true;
+                       }
+               }
+               return false;
+               */
+        //return node.id in this.adjacencies;
+    },
+
+    /*
+     Method: adjacentWithDirectionTo
+
+     Indicates if the node has a directed edge to the node specified by id
+
+     Parameters:
+
+     id - (string) A node id.
+
+     Example:
+     (start code js)
+     node.adjacentWithDirectionTo('nodeId') == true;
+     (end code)
+     */
+    adjacentWithDirectionTo: function(node) {
+        var areNeighbors = node.id in this.adjacencies;
+        if (!areNeighbors) {
+            return false;
+        }
+
+        var direction = this.adjacencies[node.id].data.$direction;
+        return direction[0] === this.id ;
+    },
+
+    /*
+       Method: getAdjacency
+    
+       Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
+
+       Parameters:
+    
+          id - (string) A node id.
+    */  
+    getAdjacency: function(id) {
+        return this.adjacencies[id];
+    },
+
+    /*
+      Method: getPos
+   
+      Returns the position of the node.
+  
+      Parameters:
+   
+         type - (string) Default's *current*. Possible values are "start", "end" or "current".
+   
+      Returns:
+   
+        A <Complex> or <Polar> instance.
+  
+      Example:
+      (start code js)
+       var pos = node.getPos('end');
+      (end code)
+   */
+   getPos: function(type) {
+       type = type || "current";
+       if(type == "current") {
+         return this.pos;
+       } else if(type == "end") {
+         return this.endPos;
+       } else if(type == "start") {
+         return this.startPos;
+       }
+   },
+   /*
+     Method: setPos
+  
+     Sets the node's position.
+  
+     Parameters:
+  
+        value - (object) A <Complex> or <Polar> instance.
+        type - (string) Default's *current*. Possible values are "start", "end" or "current".
+  
+     Example:
+     (start code js)
+      node.setPos(new $jit.Complex(0, 0), 'end');
+     (end code)
+  */
+  setPos: function(value, type) {
+      type = type || "current";
+      var pos;
+      if(type == "current") {
+        pos = this.pos;
+      } else if(type == "end") {
+        pos = this.endPos;
+      } else if(type == "start") {
+        pos = this.startPos;
+      }
+      pos.set(value);
+  }
+});
+
+MultiGraph.Node.implement(Accessors);
+
+/*
+     Class: Graph.Adjacence
+
+     A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
+     
+     Implements:
+     
+     <Accessors> methods.
+
+     See also:
+
+     <Graph>, <Graph.Node>
+
+     Properties:
+     
+      nodeFrom - A <Graph.Node> connected by this edge.
+      nodeTo - Another  <Graph.Node> connected by this edge.
+      data - Node data property containing a hash (i.e {}) with custom options.
+*/
+MultiGraph.Adjacence = new Class({
+  
+  initialize: function(nodeFrom, nodeTo, portFrom, portTo, data, Edge, Label) {
+    this.nodeFrom = nodeFrom;
+    this.nodeTo = nodeTo;
+    this.data = data || {};
+    this.startData = {};
+    this.endData = {};
+    this.Config = this.Edge = Edge;
+    this.Label = Label;
+
+       this.portFrom = portFrom;
+       this.portTo = portTo;
+  }
+});
+
+MultiGraph.Adjacence.implement(Accessors);
+
+/*
+   Object: Graph.Util
+
+   <Graph> traversal and processing utility object.
+   
+   Note:
+   
+   For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
+*/
+MultiGraph.Util = {
+    /*
+       filter
+    
+       For internal use only. Provides a filtering function based on flags.
+    */
+    filter: function(param) {
+        if(!param || !($.type(param) == 'string')) return function() { return true; };
+        var props = param.split(" ");
+        return function(elem) {
+            for(var i=0; i<props.length; i++) { 
+              if(elem[props[i]]) { 
+                return false; 
+              }
+            }
+            return true;
+        };
+    },
+    /*
+       Method: getNode
+    
+       Returns a <Graph.Node> by *id*.
+       
+       Also implemented by:
+       
+       <Graph>
+
+       Parameters:
+
+       graph - (object) A <Graph> instance.
+       id - (string) A <Graph.Node> id.
+
+       Example:
+
+       (start code js)
+         $jit.Graph.Util.getNode(graph, 'nodeid');
+         //or...
+         graph.getNode('nodeid');
+       (end code)
+    */
+    getNode: function(graph, id) {
+        return graph.nodes[id];
+    },
+    
+    /*
+       Method: eachNode
+    
+       Iterates over <Graph> nodes performing an *action*.
+       
+       Also implemented by:
+       
+       <Graph>.
+
+       Parameters:
+
+       graph - (object) A <Graph> instance.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachNode(graph, function(node) {
+          alert(node.name);
+         });
+         //or...
+         graph.eachNode(function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachNode: function(graph, action, flags) {
+        var filter = this.filter(flags);
+        for(var i in graph.nodes) {
+          if(filter(graph.nodes[i])) action(graph.nodes[i]);
+        } 
+    },
+    
+    /*
+      Method: each
+   
+      Iterates over <Graph> nodes performing an *action*. It's an alias for <Graph.Util.eachNode>.
+      
+      Also implemented by:
+      
+      <Graph>.
+  
+      Parameters:
+  
+      graph - (object) A <Graph> instance.
+      action - (function) A callback function having a <Graph.Node> as first formal parameter.
+  
+      Example:
+      (start code js)
+        $jit.Graph.Util.each(graph, function(node) {
+         alert(node.name);
+        });
+        //or...
+        graph.each(function(node) {
+          alert(node.name);
+        });
+      (end code)
+   */
+   each: function(graph, action, flags) {
+      this.eachNode(graph, action, flags); 
+   },
+
+   eachAdjacencyBatch: function(node, action, flags) {
+               var adj = node.adjacencies;
+
+               for(var id in adj) {
+                       var a = adj[id];
+                       action(a, id);
+               }
+   },
+
+ /*
+       Method: eachAdjacency
+    
+       Iterates over <Graph.Node> adjacencies applying the *action* function.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+
+       node - (object) A <Graph.Node>.
+       action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachAdjacency(node, function(adj) {
+          alert(adj.nodeTo.name);
+         });
+         //or...
+         node.eachAdjacency(function(adj) {
+           alert(adj.nodeTo.name);
+         });
+       (end code)
+    */
+    eachAdjacency: function(node, action, flags) {
+               var adj = node.adjacencies, filter = this.filter(flags);
+
+               for(var id in adj) {
+                       var a = adj[id];
+                       for(var idj in a) {
+                               action(a[idj], id);
+                       }
+               }
+
+        /*var adj = node.adjacencies, filter = this.filter(flags);
+        for(var id in adj) {
+          var a = adj[id];
+          if(filter(a)) {
+            if(a.nodeFrom != node) {
+              var tmp = a.nodeFrom;
+              a.nodeFrom = a.nodeTo;
+              a.nodeTo = tmp;
+            }
+            action(a, id);
+          }
+        }*/
+    },
+
+     /*
+       Method: computeLevels
+    
+       Performs a BFS traversal setting the correct depth for each node.
+        
+       Also implemented by:
+       
+       <Graph>.
+       
+       Note:
+       
+       The depth of each node can then be accessed by 
+       >node._depth
+
+       Parameters:
+
+       graph - (object) A <Graph>.
+       id - (string) A starting node id for the BFS traversal.
+       startDepth - (optional|number) A minimum depth value. Default's 0.
+
+    */
+    computeLevels: function(graph, id, startDepth, flags) {
+        startDepth = startDepth || 0;
+        var filter = this.filter(flags);
+        this.eachNode(graph, function(elem) {
+            elem._flag = false;
+            elem._depth = -1;
+        }, flags);
+        var root = graph.getNode(id);
+        root._depth = startDepth;
+        var queue = [root];
+        while(queue.length != 0) {
+            var node = queue.pop();
+            node._flag = true;
+            this.eachAdjacency(node, function(adj) {
+                var n = adj.nodeTo;
+                if(n._flag == false && filter(n) && !adj._hiding) {
+                    if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
+                    queue.unshift(n);
+                }
+            }, flags);
+        }
+    },
+
+    /*
+       Method: eachBFS
+    
+       Performs a BFS traversal applying *action* to each <Graph.Node>.
+       
+       Also implemented by:
+       
+       <Graph>.
+
+       Parameters:
+
+       graph - (object) A <Graph>.
+       id - (string) A starting node id for the BFS traversal.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
+          alert(node.name);
+         });
+         //or...
+         graph.eachBFS('mynodeid', function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachBFS: function(graph, id, action, flags) {
+        var filter = this.filter(flags);
+        this.clean(graph);
+        var queue = [graph.getNode(id)];
+        while(queue.length != 0) {
+            var node = queue.pop();
+            if (!node) return;
+            node._flag = true;
+            action(node, node._depth);
+            this.eachAdjacency(node, function(adj) {
+                var n = adj.nodeTo;
+                if(n._flag == false && filter(n) && !adj._hiding) {
+                    n._flag = true;
+                    queue.unshift(n);
+                }
+            }, flags);
+        }
+    },
+    
+    /*
+       Method: eachLevel
+    
+       Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
+       In case you need to break the iteration, *action* should return false.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       
+       node - (object) A <Graph.Node>.
+       levelBegin - (number) A relative level value.
+       levelEnd - (number) A relative level value.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+    */
+    eachLevel: function(node, levelBegin, levelEnd, action, flags) {
+        var d = node._depth, filter = this.filter(flags), that = this, shouldContinue = true;
+        levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
+        (function loopLevel(node, levelBegin, levelEnd) {
+            if(!shouldContinue) return;
+            var d = node._depth, ret;
+            if(d >= levelBegin && d <= levelEnd && filter(node)) ret = action(node, d);
+            if(typeof ret !== "undefined") shouldContinue = ret;
+            if(shouldContinue && d < levelEnd) {
+                that.eachAdjacency(node, function(adj) {
+                    var n = adj.nodeTo;
+                    if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
+                });
+            }
+        })(node, levelBegin + d, levelEnd + d);
+    },
+
+    /*
+       Method: eachSubgraph
+    
+       Iterates over a node's children recursively.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachSubgraph(node, function(node) {
+           alert(node.name);
+         });
+         //or...
+         node.eachSubgraph(function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachSubgraph: function(node, action, flags) {
+      this.eachLevel(node, 0, false, action, flags);
+    },
+
+    /*
+       Method: eachSubnode
+    
+       Iterates over a node's children (without deeper recursion).
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+       
+       Parameters:
+       node - (object) A <Graph.Node>.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachSubnode(node, function(node) {
+          alert(node.name);
+         });
+         //or...
+         node.eachSubnode(function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachSubnode: function(node, action, flags) {
+        this.eachLevel(node, 1, 1, action, flags);
+    },
+
+    /*
+       Method: anySubnode
+    
+       Returns *true* if any subnode matches the given condition.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+       cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
+         //or...
+         node.anySubnode(function(node) { return node.name == 'mynodename'; });
+       (end code)
+    */
+    anySubnode: function(node, cond, flags) {
+      var flag = false;
+      cond = cond || $.lambda(true);
+      var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
+      this.eachSubnode(node, function(elem) {
+        if(c(elem)) flag = true;
+      }, flags);
+      return flag;
+    },
+  
+    /*
+       Method: getSubnodes
+    
+       Collects all subnodes for a specified node. 
+       The *level* parameter filters nodes having relative depth of *level* from the root node. 
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+       level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
+
+       Returns:
+       An array of nodes.
+
+    */
+    getSubnodes: function(node, level, flags) {
+        var ans = [], that = this;
+        level = level || 0;
+        var levelStart, levelEnd;
+        if($.type(level) == 'array') {
+            levelStart = level[0];
+            levelEnd = level[1];
+        } else {
+            levelStart = level;
+            levelEnd = Number.MAX_VALUE - node._depth;
+        }
+        this.eachLevel(node, levelStart, levelEnd, function(n) {
+            ans.push(n);
+        }, flags);
+        return ans;
+    },
+  
+  
+    /*
+       Method: getParents
+    
+       Returns an Array of <Graph.Nodes> which are parents of the given node.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+
+       Returns:
+       An Array of <Graph.Nodes>.
+
+       Example:
+       (start code js)
+         var pars = $jit.Graph.Util.getParents(node);
+         //or...
+         var pars = node.getParents();
+         
+         if(pars.length > 0) {
+           //do stuff with parents
+         }
+       (end code)
+    */
+    getParents: function(node) {
+        var ans = [];
+        this.eachAdjacency(node, function(adj) {
+            var n = adj.nodeTo;
+            if(n._depth < node._depth) ans.push(n);
+        });
+        return ans;
+    },
+    
+    /*
+    Method: isDescendantOf
+    Returns a boolean indicating if some node is descendant of the node with the given id. 
+
+    Also implemented by:
+    
+    <Graph.Node>.
+    
+    
+    Parameters:
+    node - (object) A <Graph.Node>.
+    id - (string) A <Graph.Node> id.
+
+    Example:
+    (start code js)
+      $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
+      //or...
+      node.isDescendantOf('nodeid');//true|false
+    (end code)
+ */
+ isDescendantOf: function(node, id) {
+    if(node.id == id) return true;
+    var pars = this.getParents(node), ans = false;
+    for ( var i = 0; !ans && i < pars.length; i++) {
+    ans = ans || this.isDescendantOf(pars[i], id);
+  }
+    return ans;
+ },
+
+ /*
+     Method: clean
+  
+     Cleans flags from nodes.
+
+     Also implemented by:
+     
+     <Graph>.
+     
+     Parameters:
+     graph - A <Graph> instance.
+  */
+  clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
+  
+  /* 
+    Method: getClosestNodeToOrigin 
+  
+    Returns the closest node to the center of canvas.
+  
+    Also implemented by:
+    
+    <Graph>.
+    
+    Parameters:
+   
+     graph - (object) A <Graph> instance.
+     prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
+  
+  */
+  getClosestNodeToOrigin: function(graph, prop, flags) {
+   return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
+  },
+  
+  /* 
+    Method: getClosestNodeToPos
+  
+    Returns the closest node to the given position.
+  
+    Also implemented by:
+    
+    <Graph>.
+    
+    Parameters:
+   
+     graph - (object) A <Graph> instance.
+     pos - (object) A <Complex> or <Polar> instance.
+     prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
+  
+  */
+  getClosestNodeToPos: function(graph, pos, prop, flags) {
+   var node = null;
+   prop = prop || 'current';
+   pos = pos && pos.getc(true) || Complex.KER;
+   var distance = function(a, b) {
+     var d1 = a.x - b.x, d2 = a.y - b.y;
+     return d1 * d1 + d2 * d2;
+   };
+   this.eachNode(graph, function(elem) {
+     node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
+         node.getPos(prop).getc(true), pos)) ? elem : node;
+   }, flags);
+   return node;
+  } 
+};
+
+//Append graph methods to <Graph>
+$.each(['get', 'getNode', 'each', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
+  MultiGraph.prototype[m] = function() {
+    return MultiGraph.Util[m].apply(MultiGraph.Util, [this].concat(Array.prototype.slice.call(arguments)));
+  };
+});
+
+//Append node methods to <Graph.Node>
+$.each(['eachAdjacencyBatch', 'eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
+  MultiGraph.Node.prototype[m] = function() {
+    return MultiGraph.Util[m].apply(MultiGraph.Util, [this].concat(Array.prototype.slice.call(arguments)));
+  };
+});
+
+
+/*
+ * File: Graph.Op.js
+ *
+*/
+
+/*
+   Object: Graph.Op
+
+   Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
+   morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
+
+*/
+MultiGraph.Op = {
+
+    options: {
+      type: 'nothing',
+      duration: 2000,
+      hideLabels: true,
+      fps:30
+    },
+    
+    initialize: function(viz) {
+      this.viz = viz;
+    },
+
+    /*
+       Method: removeNode
+    
+       Removes one or more <Graph.Nodes> from the visualization. 
+       It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
+
+       Parameters:
+    
+        node - (string|array) The node's id. Can also be an array having many ids.
+        opt - (object) Animation options. It's an object with optional properties described below
+        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
+        duration - Described in <Options.Fx>.
+        fps - Described in <Options.Fx>.
+        transition - Described in <Options.Fx>.
+        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+   
+      Example:
+      (start code js)
+        var viz = new $jit.Viz(options);
+        viz.op.removeNode('nodeId', {
+          type: 'fade:seq',
+          duration: 1000,
+          hideLabels: false,
+          transition: $jit.Trans.Quart.easeOut
+        });
+        //or also
+        viz.op.removeNode(['someId', 'otherId'], {
+          type: 'fade:con',
+          duration: 1500
+        });
+      (end code)
+    */
+  
+    removeNode: function(node, opt) {
+        var viz = this.viz;
+        var options = $.merge(this.options, viz.controller, opt);
+        var n = $.splat(node);
+        var i, that, nodeObj;
+        switch(options.type) {
+            case 'nothing':
+                for(i=0; i<n.length; i++) {
+                    options.onBeforeRemoveNode(viz.graph.getNode(n[i]));
+                    viz.graph.removeNode(n[i]);
+                }
+                break;
+            
+            case 'replot':
+                this.removeNode(n, { type: 'nothing' });
+                viz.labels.clearLabels();
+                viz.refresh(true);
+                break;
+            
+            case 'fade:seq': case 'fade':
+                that = this;
+                //set alpha to 0 for nodes to remove.
+                for(i=0; i<n.length; i++) {
+                    nodeObj = viz.graph.getNode(n[i]);
+                    nodeObj.setData('alpha', 0, 'end');
+                }
+                viz.fx.animate($.merge(options, {
+                    modes: ['node-property:alpha'],
+                    onComplete: function() {
+                        that.removeNode(n, { type: 'nothing' });
+                        viz.labels.clearLabels();
+                        viz.reposition();
+                        viz.fx.animate($.merge(options, {
+                            modes: ['linear']
+                        }));
+                    }
+                }));
+                break;
+            
+            case 'fade:con':
+                that = this;
+                //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
+                for(i=0; i<n.length; i++) {
+                    nodeObj = viz.graph.getNode(n[i]);
+                    nodeObj.setData('alpha', 0, 'end');
+                    nodeObj.ignore = true;
+                }
+                viz.reposition();
+                viz.fx.animate($.merge(options, {
+                    modes: ['node-property:alpha', 'linear'],
+                    onComplete: function() {
+                        that.removeNode(n, { type: 'nothing' });
+                        options.onComplete && options.onComplete();
+                    }
+                }));
+                break;
+            
+            case 'iter':
+                that = this;
+                viz.fx.sequence({
+                    condition: function() { return n.length != 0; },
+                    step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
+                    onComplete: function() { options.onComplete && options.onComplete(); },
+                    duration: Math.ceil(options.duration / n.length)
+                });
+                break;
+                
+            default: this.doError();
+        }
+    },
+    
+    /*
+       Method: removeEdge
+    
+       Removes one or more <Graph.Adjacences> from the visualization. 
+       It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
+
+       Parameters:
+    
+       vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
+       opt - (object) Animation options. It's an object with optional properties described below
+       type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
+       duration - Described in <Options.Fx>.
+       fps - Described in <Options.Fx>.
+       transition - Described in <Options.Fx>.
+       hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+   
+      Example:
+      (start code js)
+        var viz = new $jit.Viz(options);
+        viz.op.removeEdge(['nodeId', 'otherId'], {
+          type: 'fade:seq',
+          duration: 1000,
+          hideLabels: false,
+          transition: $jit.Trans.Quart.easeOut
+        });
+        //or also
+        viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
+          type: 'fade:con',
+          duration: 1500
+        });
+      (end code)
+    
+    */
+    removeEdge: function(vertex, opt) {
+        var viz = this.viz;
+        var options = $.merge(this.options, viz.controller, opt);
+        var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
+        var i, that, adj;
+        switch(options.type) {
+            case 'nothing':
+                for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
+                break;
+            
+            case 'replot':
+                this.removeEdge(v, { type: 'nothing' });
+                viz.refresh(true);
+                break;
+            
+            case 'fade:seq': case 'fade':
+                that = this;
+                //set alpha to 0 for edges to remove.
+                for(i=0; i<v.length; i++) {
+                    adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
+                    if(adj) {
+                        adj.setData('alpha', 0,'end');
+                    }
+                }
+                viz.fx.animate($.merge(options, {
+                    modes: ['edge-property:alpha'],
+                    onComplete: function() {
+                        that.removeEdge(v, { type: 'nothing' });
+                        viz.reposition();
+                        viz.fx.animate($.merge(options, {
+                            modes: ['linear']
+                        }));
+                    }
+                }));
+                break;
+            
+            case 'fade:con':
+                that = this;
+                //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
+                for(i=0; i<v.length; i++) {
+                    adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
+                    if(adj) {
+                        adj.setData('alpha',0 ,'end');
+                        adj.ignore = true;
+                    }
+                }
+                viz.reposition();
+                viz.fx.animate($.merge(options, {
+                    modes: ['edge-property:alpha', 'linear'],
+                    onComplete: function() {
+                        that.removeEdge(v, { type: 'nothing' });
+                        options.onComplete && options.onComplete();
+                    }
+                }));
+                break;
+            
+            case 'iter':
+                that = this;
+                viz.fx.sequence({
+                    condition: function() { return v.length != 0; },
+                    step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
+                    onComplete: function() { options.onComplete(); },
+                    duration: Math.ceil(options.duration / v.length)
+                });
+                break;
+                
+            default: this.doError();
+        }
+    },
+    
+    /*
+       Method: sum
+    
+       Adds a new graph to the visualization. 
+       The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
+       The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
+
+       Parameters:
+    
+       json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
+       opt - (object) Animation options. It's an object with optional properties described below
+       type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
+       duration - Described in <Options.Fx>.
+       fps - Described in <Options.Fx>.
+       transition - Described in <Options.Fx>.
+       hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+   
+      Example:
+      (start code js)
+        //...json contains a tree or graph structure...
+
+        var viz = new $jit.Viz(options);
+        viz.op.sum(json, {
+          type: 'fade:seq',
+          duration: 1000,
+          hideLabels: false,
+          transition: $jit.Trans.Quart.easeOut
+        });
+        //or also
+        viz.op.sum(json, {
+          type: 'fade:con',
+          duration: 1500
+        });
+      (end code)
+    
+    */
+    sum: function(json, opt) {
+        var viz = this.viz;
+        var options = $.merge(this.options, viz.controller, opt), root = viz.root;
+        var graph;
+        viz.root = opt.id || viz.root;
+        switch(options.type) {
+            case 'nothing':
+                graph = viz.construct(json);
+                graph.eachNode(function(elem) {
+                    elem.eachAdjacency(function(adj) {
+                        viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
+                    });
+                });
+                break;
+            
+            case 'replot':
+                viz.refresh(true);
+                this.sum(json, { type: 'nothing' });
+                viz.refresh(true);
+                break;
+            
+            case 'fade:seq': case 'fade': case 'fade:con':
+                that = this;
+                graph = viz.construct(json);
+
+                //set alpha to 0 for nodes to add.
+                var fadeEdges = this.preprocessSum(graph);
+                var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
+                viz.reposition();
+                if(options.type != 'fade:con') {
+                    viz.fx.animate($.merge(options, {
+                        modes: ['linear'],
+                        onComplete: function() {
+                            viz.fx.animate($.merge(options, {
+                                modes: modes,
+                                onComplete: function() {
+                                    options.onComplete();
+                                }
+                            }));
+                        }
+                    }));
+                } else {
+                    viz.graph.eachNode(function(elem) {
+                        if (elem.id != root && elem.pos.isZero()) {
+                          elem.pos.set(elem.endPos); 
+                          elem.startPos.set(elem.endPos);
+                        }
+                    });
+                    viz.fx.animate($.merge(options, {
+                        modes: ['linear'].concat(modes)
+                    }));
+                }
+                break;
+
+            default: this.doError();
+        }
+    },
+    
+    /*
+       Method: morph
+    
+       This method will transform the current visualized graph into the new JSON representation passed in the method. 
+       The JSON object must at least have the root node in common with the current visualized graph.
+
+       Parameters:
+    
+       json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
+       opt - (object) Animation options. It's an object with optional properties described below
+       type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
+       duration - Described in <Options.Fx>.
+       fps - Described in <Options.Fx>.
+       transition - Described in <Options.Fx>.
+       hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+       id - (string) The shared <Graph.Node> id between both graphs.
+       
+       extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
+                    *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
+                    For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
+                    properties as values, just like specified in <Graph.Plot.animate>.
+   
+      Example:
+      (start code js)
+        //...json contains a tree or graph structure...
+
+        var viz = new $jit.Viz(options);
+        viz.op.morph(json, {
+          type: 'fade',
+          duration: 1000,
+          hideLabels: false,
+          transition: $jit.Trans.Quart.easeOut
+        });
+        //or also
+        viz.op.morph(json, {
+          type: 'fade',
+          duration: 1500
+        });
+        //if the json data contains dollar prefixed params
+        //like $width or $height these too can be animated
+        viz.op.morph(json, {
+          type: 'fade',
+          duration: 1500
+        }, {
+          'node-property': ['width', 'height']
+        });
+      (end code)
+    
+    */
+    morph: function(json, opt, extraModes) {
+        extraModes = extraModes || {};
+        var viz = this.viz;
+        var options = $.merge(this.options, viz.controller, opt), root = viz.root;
+        var graph;
+        //TODO(nico) this hack makes morphing work with the Hypertree. 
+        //Need to check if it has been solved and this can be removed.
+        viz.root = opt.id || viz.root;
+        switch(options.type) {
+            case 'nothing':
+                graph = viz.construct(json);
+                graph.eachNode(function(elem) {
+                  var nodeExists = viz.graph.hasNode(elem.id);  
+                  elem.eachAdjacency(function(adj) {
+                    var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+                    viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
+                    //Update data properties if the node existed
+                    if(adjExists) {
+                      var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+                      for(var prop in (adj.data || {})) {
+                        addedAdj.data[prop] = adj.data[prop];
+                      }
+                    }
+                  });
+                  //Update data properties if the node existed
+                  if(nodeExists) {
+                    var addedNode = viz.graph.getNode(elem.id);
+                    for(var prop in (elem.data || {})) {
+                      addedNode.data[prop] = elem.data[prop];
+                    }
+                  }
+                });
+                viz.graph.eachNode(function(elem) {
+                    elem.eachAdjacency(function(adj) {
+                        if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
+                            viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+                        }
+                    });
+                    if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
+                });
+                
+                break;
+            
+            case 'replot':
+                viz.labels.clearLabels(true);
+                this.morph(json, { type: 'nothing' });
+                viz.refresh(true);
+                viz.refresh(true);
+                break;
+                
+            case 'fade:seq': case 'fade': case 'fade:con':
+                that = this;
+                graph = viz.construct(json);
+                //preprocessing for nodes to delete.
+                //get node property modes to interpolate
+                var nodeModes = ('node-property' in extraModes) 
+                  && $.map($.splat(extraModes['node-property']), 
+                      function(n) { return '$' + n; });
+                viz.graph.eachNode(function(elem) {
+                  var graphNode = graph.getNode(elem.id);   
+                  if(!graphNode) {
+                      elem.setData('alpha', 1);
+                      elem.setData('alpha', 1, 'start');
+                      elem.setData('alpha', 0, 'end');
+                      elem.ignore = true;
+                    } else {
+                      //Update node data information
+                      var graphNodeData = graphNode.data;
+                      for(var prop in graphNodeData) {
+                        if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
+                          elem.endData[prop] = graphNodeData[prop];
+                        } else {
+                          elem.data[prop] = graphNodeData[prop];
+                        }
+                      }
+                    }
+                }); 
+                viz.graph.eachNode(function(elem) {
+                    if(elem.ignore) return;
+                    elem.eachAdjacency(function(adj) {
+                        if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
+                        var nodeFrom = graph.getNode(adj.nodeFrom.id);
+                        var nodeTo = graph.getNode(adj.nodeTo.id);
+                        if(!nodeFrom.adjacentTo(nodeTo)) {
+                            var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
+                            fadeEdges = true;
+                            adj.setData('alpha', 1);
+                            adj.setData('alpha', 1, 'start');
+                            adj.setData('alpha', 0, 'end');
+                            adj._hiding = true;
+                        } else if (adj.data.$direction && adj.data.$direction[0] === nodeFrom.id) {
+                            // only check one direction (from -> to)
+                            if (!nodeFrom.adjacentWithDirectionTo(nodeTo)) {
+                                adj._reversing = true;
+                            }
+                        }
+                    });
+                }); 
+                //preprocessing for adding nodes.
+                var fadeEdges = this.preprocessSum(graph);
+
+                var modes = !fadeEdges? ['node-property:alpha'] : 
+                                        ['node-property:alpha', 
+                                         'edge-property:alpha'];
+                //Append extra node-property animations (if any)
+                modes[0] = modes[0] + (('node-property' in extraModes)? 
+                    (':' + $.splat(extraModes['node-property']).join(':')) : '');
+                //Append extra edge-property animations (if any)
+                modes[1] = (modes[1] || 'edge-property:alpha') + (('edge-property' in extraModes)? 
+                    (':' + $.splat(extraModes['edge-property']).join(':')) : '');
+                //Add label-property animations (if any)
+                if('label-property' in extraModes) {
+                  modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
+                }
+                //only use reposition if its implemented.
+                if (viz.reposition) {
+                  viz.reposition();
+                } else {
+                  viz.compute('end');
+                }
+                this._updateDirectedEdges();
+
+                viz.graph.eachNode(function(elem) {
+                    if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
+                      elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
+                    }
+                });
+                viz.fx.animate($.merge(options, {
+                    modes: [extraModes.position || 'polar'].concat(modes),
+                    onComplete: function() {
+                        viz.graph.eachNode(function(elem) {
+                            if(elem.ignore) viz.graph.removeNode(elem.id);
+                        });
+                        viz.graph.eachNode(function(elem) {
+                            elem.eachAdjacency(function(adj) {
+                                if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+                            });
+                        });
+                        options.onComplete();
+                    }
+                }));
+                break;
+
+            default:;
+        }
+    },
+
+    _updateDirectedEdges: function () {
+        var graph = this.viz.graph;
+        graph.eachNode(function(node) {
+            node.eachAdjacency(function (adj) {
+
+                var isDirectedEdge = adj.data.$direction;
+                if (isDirectedEdge && adj.nodeFrom.id !== adj.data.$direction[0]) {
+                    return;
+                }
+
+                if (adj._hiding) {
+                    graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+                }
+
+                if (adj._reversing) {
+                    var from = adj.nodeFrom.id;
+                    var to = adj.nodeTo.id;
+//
+//                    // swap instead of adding and removing
+                    var edge1 = graph.edges[from][to];
+                    var edge2 = graph.edges[to][from];
+
+                    edge1.data.$direction = [to, from];
+                    edge2.data.$direction = [to, from];
+
+                    adj._reversing = undefined;
+                }
+            });
+        });
+    },
+    
+  /*
+    Method: contract
+    Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
+    
+    Parameters:
+    node - (object) A <Graph.Node>.
+    opt - (object) An object containing options described below
+    type - (string) Whether to 'replot' or 'animate' the contraction.
+   
+    There are also a number of Animation options. For more information see <Options.Fx>.
+
+    Example:
+    (start code js)
+     var viz = new $jit.Viz(options);
+     viz.op.contract(node, {
+       type: 'animate',
+       duration: 1000,
+       hideLabels: true,
+       transition: $jit.Trans.Quart.easeOut
+     });
+   (end code)
+   */
+    contract: function(node, opt) {
+      var viz = this.viz;
+      if(node.collapsed || !node.anySubnode($.lambda(true))) return;
+      opt = $.merge(this.options, viz.config, opt || {}, {
+        'modes': ['node-property:alpha:span', 'linear']
+      });
+      node.collapsed = true;
+      (function subn(n) {
+        n.eachSubnode(function(ch) {
+          ch.ignore = true;
+          ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
+          subn(ch);
+        });
+      })(node);
+      if(opt.type == 'animate') {
+        viz.compute('end');
+        if(viz.rotated) {
+          viz.rotate(viz.rotated, 'none', {
+            'property':'end'
+          });
+        }
+        (function subn(n) {
+          n.eachSubnode(function(ch) {
+            ch.setPos(node.getPos('end'), 'end');
+            subn(ch);
+          });
+        })(node);
+        viz.fx.animate(opt);
+      } else if(opt.type == 'replot'){
+        viz.refresh();
+      }
+    },
+    
+    /*
+    Method: expand
+    Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
+    
+    Parameters:
+    node - (object) A <Graph.Node>.
+    opt - (object) An object containing options described below
+    type - (string) Whether to 'replot' or 'animate'.
+     
+    There are also a number of Animation options. For more information see <Options.Fx>.
+
+    Example:
+    (start code js)
+      var viz = new $jit.Viz(options);
+      viz.op.expand(node, {
+        type: 'animate',
+        duration: 1000,
+        hideLabels: true,
+        transition: $jit.Trans.Quart.easeOut
+      });
+    (end code)
+   */
+    expand: function(node, opt) {
+      if(!('collapsed' in node)) return;
+      var viz = this.viz;
+      opt = $.merge(this.options, viz.config, opt || {}, {
+        'modes': ['node-property:alpha:span', 'linear']
+      });
+      delete node.collapsed;
+      (function subn(n) {
+        n.eachSubnode(function(ch) {
+          delete ch.ignore;
+          ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
+          subn(ch);
+        });
+      })(node);
+      if(opt.type == 'animate') {
+        viz.compute('end');
+        if(viz.rotated) {
+          viz.rotate(viz.rotated, 'none', {
+            'property':'end'
+          });
+        }
+        viz.fx.animate(opt);
+      } else if(opt.type == 'replot'){
+        viz.refresh();
+      }
+    },
+
+    preprocessSum: function(graph) {
+        var viz = this.viz;
+        graph.eachNode(function(elem) {
+            if(!viz.graph.hasNode(elem.id)) {
+                viz.graph.addNode(elem);
+                var n = viz.graph.getNode(elem.id);
+                n.setData('alpha', 0);
+                n.setData('alpha', 0, 'start');
+                n.setData('alpha', 1, 'end');
+            }
+        }); 
+        var fadeEdges = false;
+        graph.eachNode(function(elem) {
+            elem.eachAdjacency(function(adj) {
+                var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
+                var nodeTo = viz.graph.getNode(adj.nodeTo.id);
+                if(!nodeFrom.adjacentTo(nodeTo)) {
+                    var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
+                    if(nodeFrom.startAlpha == nodeFrom.endAlpha 
+                    && nodeTo.startAlpha == nodeTo.endAlpha) {
+                        fadeEdges = true;
+                        adj.setData('alpha', 0);
+                        adj.setData('alpha', 0, 'start');
+                        adj.setData('alpha', 1, 'end');
+                    } 
+                }
+            });
+        }); 
+        return fadeEdges;
+    }
+};
+
+
+
+/*
+ File: Helpers.js
+
+ Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
+ Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
+ position is over the rendered shape.
+
+ Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
+ *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
+
+ Example:
+ (start code js)
+ //implement a new node type
+ $jit.Viz.Plot.NodeTypes.implement({
+ 'customNodeType': {
+ 'render': function(node, canvas) {
+ this.nodeHelper.circle.render ...
+ },
+ 'contains': function(node, pos) {
+ this.nodeHelper.circle.contains ...
+ }
+ }
+ });
+ //implement an edge type
+ $jit.Viz.Plot.EdgeTypes.implement({
+ 'customNodeType': {
+ 'render': function(node, canvas) {
+ this.edgeHelper.circle.render ...
+ },
+ //optional
+ 'contains': function(node, pos) {
+ this.edgeHelper.circle.contains ...
+ }
+ }
+ });
+ (end code)
+
+ */
+
+/*
+ Object: NodeHelper
+
+ Contains rendering and other type of primitives for simple shapes.
+ */
+var MultiNodeHelper = {
+    'none': {
+        'render': $.empty,
+        'contains': $.lambda(false)
+    },
+       'host': {
+               'render': function(pos, canvas, animating) {
+                       var ctx = canvas.getCtx();
+                       var img = new Image();
+                       img.src='../img/Device_pc_3045_default_64.png';
+                       if(animating == false) {
+                               img.onload = function() {
+                                       ctx.drawImage(img,pos.x-32,pos.y-32);
+                               };
+                       } else {
+                               ctx.drawImage(img,pos.x-32,pos.y-32);
+                       }
+                       /*var width = 64, height = 55;
+                       ctx.fillStyle = "rgba(0,0,0,0)";
+                       ctx.fillRect(pos.x - width / 2, pos.y - height / 2, width, height);*/
+               },
+               'contains': function(npos, pos) {
+                       var width = 64, height = 55;
+                       return Math.abs(pos.x - npos.x) <= width / 2
+                               && Math.abs(pos.y - npos.y) <= height / 2;
+               }
+       },
+       'swtch': {
+               'render': function(pos, canvas, animating) {
+                       var ctx = canvas.getCtx();
+                       var img = new Image();
+                       img.src='../img/Device_switch_3062_unknown_64.png';
+                       if(animating == false) {
+                               img.onload = function() {
+                                       ctx.drawImage(img,pos.x-32,pos.y-32);
+                               };
+                       } else {
+                               ctx.drawImage(img,pos.x-32,pos.y-32);
+                       }
+                       /*var width = 64, height = 45;
+                       ctx.fillStyle = "rgba(0,0,0,0)";
+                       ctx.fillRect(pos.x - width / 2, pos.y - height / 2, width, height);*/
+               },
+               'contains': function(npos, pos) {
+                       var width = 64, height = 45;
+                       return Math.abs(pos.x - npos.x) <= width / 2
+                               && Math.abs(pos.y - npos.y) <= height / 2;
+               }
+       }
+};
+
+/*
+ Object: EdgeHelper
+
+ Contains rendering primitives for simple edge shapes.
+ */
+var MultiEdgeHelper = {
+    /*
+     Object: EdgeHelper.line
+     */
+    'line': {
+        /*
+         Method: render
+
+         Renders a line into the canvas.
+
+         Parameters:
+
+         from - (object) An *x*, *y* object with the starting position of the line.
+         to - (object) An *x*, *y* object with the ending position of the line.
+         canvas - (object) A <Canvas> instance.
+
+         Example:
+         (start code js)
+         EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
+         (end code)
+         */
+        'render': function(from, to, alpha, canvas){
+                       alpha *= 8;
+            var ctx = canvas.getCtx();
+                       ctx.beginPath();
+            
+                       // trig path
+                       dx = from.x-to.x; dy = from.y-to.y;
+                       tan = Math.atan(dx/dy);
+                       cos = (-1)*Math.cos(tan);
+                       sin = Math.sin(tan);
+
+                       // draw the line
+            ctx.moveTo(from.x+(alpha*cos), from.y+(alpha*sin));
+            ctx.lineTo(to.x+(alpha*cos), to.y+(alpha*sin));
+            ctx.stroke();
+        },
+        /*
+         Method: contains
+
+         Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+         Parameters:
+
+         posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
+         posTo - (object) An *x*, *y* object with a <Graph.Node> position.
+         pos - (object) An *x*, *y* object with the position to check.
+         epsilon - (number) The dimension of the shape.
+
+         Example:
+         (start code js)
+         EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
+         (end code)
+         */
+        'contains': function(posFrom, posTo, alpha, pos, epsilon, canvas) {
+                       alpha *= 8;
+                       var ctx = canvas.getCtx();
+                       ctx.save();
+                       ctx.beginPath();
+
+                       // trig path
+                       dx = posFrom.x-posTo.x; dy = posFrom.y-posTo.y;
+                       tan = Math.atan(dx/dy);
+                       cos = (-1)*Math.cos(tan);
+                       sin = Math.sin(tan);
+
+                       // draw rect
+                       var width = 2.5;
+                       ctx.moveTo(posFrom.x+((alpha-width)*cos), posFrom.y+((alpha-width)*sin));
+                       ctx.lineTo(posTo.x+((alpha-width)*cos), posTo.y+((alpha-width)*sin));
+                       ctx.lineTo(posTo.x+((alpha+width)*cos), posTo.y+((alpha+width)*sin));
+                       ctx.lineTo(posFrom.x+((alpha+width)*cos), posFrom.y+((alpha+width)*sin));
+
+                       // check if point is in path
+                       ox = ctx.canvas.offsetWidth/2;
+                       oy = ctx.canvas.offsetHeight/2;
+                       sx = canvas.scaleOffsetX;
+                       sy = canvas.scaleOffsetY;
+                       tx = canvas.translateOffsetX;
+                       ty = canvas.translateOffsetY;
+                       if (ctx.isPointInPath((tx+ox+pos.x*sx), (ty+oy+pos.y*sy))) {
+                               ctx.restore();
+                               return true;
+                       }
+
+                       ctx.restore();
+                       return false;
+        }
+    }
+};
+
+
+       /*
+ * File: Graph.Plot.js
+ */
+
+/*
+   Object: Graph.Plot
+
+   <Graph> rendering and animation methods.
+   
+   Properties:
+   
+   nodeHelper - <NodeHelper> object.
+   edgeHelper - <EdgeHelper> object.
+*/
+MultiGraph.Plot = {
+    //Default initializer
+    initialize: function(viz, klass){
+      this.viz = viz;
+      this.config = viz.config;
+      this.node = viz.config.Node;
+      this.edge = viz.config.Edge;
+      this.animation = new Animation;
+      this.nodeTypes = new klass.Plot.NodeTypes;
+      this.edgeTypes = new klass.Plot.EdgeTypes;
+      this.labels = viz.labels;
+   },
+
+    //Add helpers
+    nodeHelper: MultiNodeHelper,
+    edgeHelper: MultiEdgeHelper,
+    
+    Interpolator: {
+        //node/edge property parsers
+        'map': {
+          'border': 'color',
+          'color': 'color',
+          'width': 'number',
+          'height': 'number',
+          'dim': 'number',
+          'alpha': 'number',
+          'lineWidth': 'number',
+          'angularWidth':'number',
+          'span':'number',
+          'valueArray':'array-number',
+          'dimArray':'array-number',
+          'vertices':'polygon'
+          //'colorArray':'array-color'
+        },
+
+        //canvas specific parsers
+        'canvas': {
+          'globalAlpha': 'number',
+          'fillStyle': 'color',
+          'strokeStyle': 'color',
+          'lineWidth': 'number',
+          'shadowBlur': 'number',
+          'shadowColor': 'color',
+          'shadowOffsetX': 'number',
+          'shadowOffsetY': 'number',
+          'miterLimit': 'number'
+        },
+
+        //label parsers
+        'label': {
+          'size': 'number',
+          'color': 'color'
+        },
+
+        //Number interpolator
+        'compute': function(from, to, delta) {
+          return from + (to - from) * delta;
+        },
+
+        //Position interpolators
+        'moebius': function(elem, props, delta, vector) {
+          var v = vector.scale(-delta);
+          if(v.norm() < 1) {
+              var x = v.x, y = v.y;
+              var ans = elem.startPos
+                .getc().moebiusTransformation(v);
+              elem.pos.setc(ans.x, ans.y);
+              v.x = x; v.y = y;
+            }
+        },
+
+        'linear': function(elem, props, delta) {
+            var from = elem.startPos.getc(true);
+            var to = elem.endPos.getc(true);
+            elem.pos.setc(this.compute(from.x, to.x, delta),
+                          this.compute(from.y, to.y, delta));
+        },
+
+        'polar': function(elem, props, delta) {
+          var from = elem.startPos.getp(true);
+          var to = elem.endPos.getp();
+          var ans = to.interpolate(from, delta);
+          elem.pos.setp(ans.theta, ans.rho);
+        },
+
+        //Graph's Node/Edge interpolators
+        'number': function(elem, prop, delta, getter, setter) {
+                       var from, to;
+                       if(Array.isArray(elem)) { // for multi-links
+                               for(var e in elem) {
+                                       var ee = elem[e];
+                                       from = ee[getter](prop, 'start');
+                                       to = ee[getter](prop, 'end');
+                                       if (from != to) {
+                                               elem = ee;
+                                               break;
+                                       }
+                               }
+                               if(Array.isArray(elem)) return;
+                       } else { // for nodes
+                         from = elem[getter](prop, 'start');
+                         to = elem[getter](prop, 'end');
+                   }
+                       elem[setter](prop, this.compute(from, to, delta));
+        },
+
+        'color': function(elem, prop, delta, getter, setter) {
+                         var from, to;
+                         if(Array.isArray(elem)) { // for multi-links
+                               for(var e in elem) {
+                                       var ee = elem[e];
+                                       from = $.hexToRgb(ee[getter](prop, 'start'));
+                                       to = $.hexToRgb(ee[getter](prop, 'end'));
+                                       if(from[0] != to[0] || from[1] != to[1] || from[2] != to[2]) {
+                                               elem = ee;
+                                               break;
+                                       }
+                               }
+                               if(Array.isArray(elem)) return;
+                         } else { // for nodes
+                                 var from = $.hexToRgb(elem[getter](prop, 'start'));
+                                 var to = $.hexToRgb(elem[getter](prop, 'end'));
+                         }
+                         var comp = this.compute;
+                         var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
+                                                                       parseInt(comp(from[1], to[1], delta)),
+                                                                       parseInt(comp(from[2], to[2], delta))]);
+                         elem[setter](prop, val);
+        },
+
+        'array-number': function(elem, prop, delta, getter, setter) {
+          var from = elem[getter](prop, 'start'),
+              to = elem[getter](prop, 'end'),
+              cur = [];
+          for(var i=0, l=from.length; i<l; i++) {
+            var fromi = from[i], toi = to[i];
+            if(fromi.length) {
+              for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
+                curi.push(this.compute(fromi[j], toi[j], delta));
+              }
+              cur.push(curi);
+            } else {
+              cur.push(this.compute(fromi, toi, delta));
+            }
+          }
+          elem[setter](prop, cur);
+        },
+
+        'node': function(elem, props, delta, map, getter, setter) {
+          map = this[map];
+          if(props) {
+            var len = props.length;
+            for(var i=0; i<len; i++) {
+              var pi = props[i];
+              this[map[pi]](elem, pi, delta, getter, setter);
+            }
+          } else {
+            for(var pi in map) {
+              this[map[pi]](elem, pi, delta, getter, setter);
+            }
+          }
+        },
+
+        'edge': function(elem, props, delta, mapKey, getter, setter) {
+            var adjs = elem.adjacencies;
+            for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
+        },
+
+        'node-property': function(elem, props, delta) {
+          this['node'](elem, props, delta, 'map', 'getData', 'setData');
+        },
+
+        'edge-property': function(elem, props, delta) {
+          this['edge'](elem, props, delta, 'map', 'getData', 'setData');
+        },
+
+        'label-property': function(elem, props, delta) {
+          this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
+        },
+
+        'node-style': function(elem, props, delta) {
+          this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
+        },
+
+        'edge-style': function(elem, props, delta) {
+          this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
+        },
+
+        'polygon': function(elem, prop, delta, getter, setter) {
+          var from = elem[getter](prop, 'start'),
+              to = elem[getter](prop, 'end'),
+              cur = [];
+          if (typeof from.offset == 'undefined') {
+            if (from === 0) {
+              from = $.map(to, function() {
+                return new $jit.Complex(0, 0);
+              });
+              from.offset = 0;
+            }
+            else {
+              if (from.length == 0) {
+                from.push(new $jit.Complex(0, 0));
+              }
+              while (from.length < to.length) {
+                from.push(from[0]);
+              }
+              while (from.length > to.length) {
+                to.push(to[0] || new $jit.Complex(0, 0));
+              }
+              if (from.length == 0) return;
+              var l = from.length;
+              var minDist = 1e300;
+              for (var offset = 0; offset < l; offset ++) {
+                var d = 0;
+                for (var i = 0; i < l; i++) {
+                  d +=Geometry.dist2(from[(offset + i) % l], to[i]);
+                }
+                if (d < minDist) {
+                  from.offset = offset;
+                  minDist = d;
+                }
+              }
+            }
+          }
+
+          for (var i = 0, l = from.length; i < l; i++) {
+            var fromi = from[(i + from.offset) % l], toi = to[i];
+            cur.push(new $jit.Complex(
+                this.compute(fromi.x, toi.x, delta),
+                this.compute(fromi.y, toi.y, delta)
+            ));
+          }
+          elem[setter](prop, cur);
+        }
+    },
+    
+  
+    /*
+       sequence
+    
+       Iteratively performs an action while refreshing the state of the visualization.
+
+       Parameters:
+
+       options - (object) An object containing some sequence options described below
+       condition - (function) A function returning a boolean instance in order to stop iterations.
+       step - (function) A function to execute on each step of the iteration.
+       onComplete - (function) A function to execute when the sequence finishes.
+       duration - (number) Duration (in milliseconds) of each step.
+
+      Example:
+       (start code js)
+        var rg = new $jit.RGraph(options);
+        var i = 0;
+        rg.fx.sequence({
+          condition: function() {
+           return i == 10;
+          },
+          step: function() {
+            alert(i++);
+          },
+          onComplete: function() {
+           alert('done!');
+          }
+        });
+       (end code)
+
+    */
+    sequence: function(options) {
+        var that = this;
+        options = $.merge({
+          condition: $.lambda(false),
+          step: $.empty,
+          onComplete: $.empty,
+          duration: 200
+        }, options || {});
+
+        var interval = setInterval(function() {
+          if(options.condition()) {
+            options.step();
+          } else {
+            clearInterval(interval);
+            options.onComplete();
+          }
+          that.viz.refresh(true);
+        }, options.duration);
+    },
+    
+    /*
+      prepare
+      Prepare graph position and other attribute values before performing an Animation. 
+      This method is used internally by the Toolkit.
+      
+      See also:
+       
+       <Animation>, <Graph.Plot.animate>
+
+    */
+    prepare: function(modes) {
+      var graph = this.viz.graph,
+          accessors = {
+            'node-property': {
+              'getter': 'getData',
+              'setter': 'setData'
+            },
+            'edge-property': {
+              'getter': 'getData',
+              'setter': 'setData'
+            },
+            'node-style': {
+              'getter': 'getCanvasStyle',
+              'setter': 'setCanvasStyle'
+            },
+            'edge-style': {
+              'getter': 'getCanvasStyle',
+              'setter': 'setCanvasStyle'
+            }
+          };
+
+      //parse modes
+      var m = {};
+      if($.type(modes) == 'array') {
+        for(var i=0, len=modes.length; i < len; i++) {
+          var elems = modes[i].split(':');
+          m[elems.shift()] = elems;
+        }
+      } else {
+        for(var p in modes) {
+          if(p == 'position') {
+            m[modes.position] = [];
+          } else {
+            m[p] = $.splat(modes[p]);
+          }
+        }
+      }
+      
+      graph.eachNode(function(node) { 
+        node.startPos.set(node.pos);
+        $.each(['node-property', 'node-style'], function(p) {
+          if(p in m) {
+            var prop = m[p];
+            for(var i=0, l=prop.length; i < l; i++) {
+              node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
+            }
+          }
+        });
+        $.each(['edge-property', 'edge-style'], function(p) {
+          if(p in m) {
+            var prop = m[p];
+            node.eachAdjacency(function(adj) {
+              for(var i=0, l=prop.length; i < l; i++) {
+                adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
+              }
+            });
+          }
+        });
+      });
+      return m;
+    },
+    
+    /*
+       Method: animate
+    
+       Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
+
+       Parameters:
+
+       opt - (object) Animation options. The object properties are described below
+       duration - (optional) Described in <Options.Fx>.
+       fps - (optional) Described in <Options.Fx>.
+       hideLabels - (optional|boolean) Whether to hide labels during the animation.
+       modes - (required|object) An object with animation modes (described below).
+
+       Animation modes:
+       
+       Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
+       They are represented by an object that has as keys main categories of properties to animate and as values a list 
+       of these specific properties. The properties are described below
+       
+       position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
+       node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
+       edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
+       label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
+       node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
+       edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
+
+       Example:
+       (start code js)
+       var viz = new $jit.Viz(options);
+       //...tweak some Data, CanvasStyles or LabelData properties...
+       viz.fx.animate({
+         modes: {
+           'position': 'linear',
+           'node-property': ['width', 'height'],
+           'node-style': 'shadowColor',
+           'label-property': 'size'
+         },
+         hideLabels: false
+       });
+       //...can also be written like this...
+       viz.fx.animate({
+         modes: ['linear',
+                 'node-property:width:height',
+                 'node-style:shadowColor',
+                 'label-property:size'],
+         hideLabels: false
+       });
+       (end code)
+    */
+    animate: function(opt, versor) {
+      opt = $.merge(this.viz.config, opt || {});
+      var that = this,
+          viz = this.viz,
+          graph  = viz.graph,
+          interp = this.Interpolator,
+          animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
+      //prepare graph values
+      var m = this.prepare(opt.modes);
+      
+      //animate
+      if(opt.hideLabels) this.labels.hideLabels(true);
+      animation.setOptions($.extend(opt, {
+        $animating: false,
+        compute: function(delta) {
+          graph.eachNode(function(node) { 
+            for(var p in m) {
+              interp[p](node, m[p], delta, versor);
+            }
+          });
+          that.plot(opt, this.$animating, delta);
+          this.$animating = true;
+        },
+        complete: function() {
+          if(opt.hideLabels) that.labels.hideLabels(false);
+          that.plot(opt);
+          opt.onComplete();
+          //TODO(nico): This shouldn't be here!
+          //opt.onAfterCompute();
+        }       
+      })).start();
+    },
+    
+    /*
+      nodeFx
+   
+      Apply animation to node properties like color, width, height, dim, etc.
+  
+      Parameters:
+  
+      options - Animation options. This object properties is described below
+      elements - The Elements to be transformed. This is an object that has a properties
+      
+      (start code js)
+      'elements': {
+        //can also be an array of ids
+        'id': 'id-of-node-to-transform',
+        //properties to be modified. All properties are optional.
+        'properties': {
+          'color': '#ccc', //some color
+          'width': 10, //some width
+          'height': 10, //some height
+          'dim': 20, //some dim
+          'lineWidth': 10 //some line width
+        } 
+      }
+      (end code)
+      
+      - _reposition_ Whether to recalculate positions and add a motion animation. 
+      This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
+      
+      - _onComplete_ A method that is called when the animation completes.
+      
+      ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
+  
+      Example:
+      (start code js)
+       var rg = new RGraph(canvas, config); //can be also Hypertree or ST
+       rg.fx.nodeFx({
+         'elements': {
+           'id':'mynodeid',
+           'properties': {
+             'color':'#ccf'
+           },
+           'transition': Trans.Quart.easeOut
+         }
+       });
+      (end code)    
+   */
+   nodeFx: function(opt) {
+     if (this.nodeFxAnimation == undefined) {
+       this.nodeFxAnimation = new Animation();
+     }
+
+     var viz = this.viz,
+         graph  = viz.graph,
+         animation = this.nodeFxAnimation,
+         options = $.merge(this.viz.config, {
+           'elements': {
+             'id': false,
+             'properties': {}
+           },
+           'reposition': false
+         });
+     opt = $.merge(options, opt || {}, {
+       onBeforeCompute: $.empty,
+       onAfterCompute: $.empty
+     });
+     //check if an animation is running
+     animation.stopTimer();
+     var props = opt.elements.properties;
+     //set end values for nodes
+     if(!opt.elements.id) {
+       graph.eachNode(function(n) {
+         for(var prop in props) {
+           n.setData(prop, props[prop], 'end');
+         }
+       });
+     } else {
+       var ids = $.splat(opt.elements.id);
+       $.each(ids, function(id) {
+         var n = graph.getNode(id);
+         if(n) {
+           for(var prop in props) {
+             n.setData(prop, props[prop], 'end');
+           }
+         }
+       });
+     }
+     //get keys
+     var propnames = [];
+     for(var prop in props) propnames.push(prop);
+     //add node properties modes
+     var modes = ['node-property:' + propnames.join(':')];
+     //set new node positions
+     if(opt.reposition) {
+       modes.push('linear');
+       viz.compute('end');
+     }
+     //animate
+     this.animate($.merge(opt, {
+       modes: modes,
+       type: 'nodefx'
+     }));
+   },
+
+    
+    /*
+       Method: plot
+    
+       Plots a <Graph>.
+
+       Parameters:
+
+       opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
+
+       Example:
+
+       (start code js)
+       var viz = new $jit.Viz(options);
+       viz.fx.plot(); 
+       (end code)
+
+    */
+   plot: function(opt, animating) {
+     var viz = this.viz, 
+         aGraph = viz.graph, 
+         canvas = viz.canvas, 
+         id = viz.root, 
+         that = this, 
+         ctx = canvas.getCtx(), 
+         min = Math.min,
+         opt = opt || this.viz.controller;
+     
+     opt.clearCanvas && canvas.clear();
+       
+     var root = aGraph.getNode(id);
+     if(!root) return;
+     
+     var T = !!root.visited;
+
+        // helper method
+               var checkDupEdge = function(e) {                                          
+                       var skip = false;                                                       
+                       for(var d in viz.graph.dup) {                                               
+                               var ee = viz.graph.dup[d];                                              
+                               if(e.nodeTo.id == ee.nodeTo.id && e.nodeFrom.id == ee.nodeFrom.id) {
+                                       if(e.portTo == ee.portTo && e.portFrom == ee.portFrom) {        
+                                               skip = true;                                                
+                                               break; // NOTE: does this break the outer for loop?         
+                                       }                                                               
+                               }                                                                   
+                       }                                                                       
+                       return skip;                                                            
+               };                                                                        
+                                                                                                                                                          
+
+        // plot each line
+        var that = this;
+     aGraph.eachNode(function(node) {
+               node.eachAdjacencyBatch(function(adj) {
+                       // plot this line if it hasn't been plotted
+                       var total = adj.length;
+                       if (total == 1) {
+                               if(!checkDupEdge(adj[0])) // plot this edge if it isn't a dup
+                                       that.plotLine(adj[0], canvas, animating);
+                       } else {
+                               for(var pos in adj) {
+                                       var a = adj[pos];
+
+                                       // check if this edge is in duplicates
+                                       if(checkDupEdge(a)) continue; // skip plot this edge if it's a dup
+
+                                       that.plotBezierLine(a, pos, total, canvas, animating);
+                               }
+                       }
+               });
+
+          /*
+       var nodeAlpha = node.getData('alpha');
+       node.eachAdjacency(function(adj) {
+         var nodeTo = adj.nodeTo;
+         if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
+           !animating && opt.onBeforePlotLine(adj);
+           that.plotLine(adj, canvas, animating);
+           !animating && opt.onAfterPlotLine(adj);
+         }
+       });
+       if(node.drawn) {
+         !animating && opt.onBeforePlotNode(node);
+         that.plotNode(node, canvas, animating);
+         !animating && opt.onAfterPlotNode(node);
+       }
+       if(!that.labelsHidden && opt.withLabels) {
+         if(node.drawn && nodeAlpha >= 0.95) {
+           that.labels.plotLabel(canvas, node, opt);
+         } else {
+           that.labels.hideLabel(node, false);
+         }
+       }
+       node.visited = !T;
+          */
+     });
+
+        // plot all of the nodes
+        if (opt.modes != undefined && opt.modes.length > 0 && opt.modes[0] == "edge-property:color") {
+               animating = true; // HACK
+        }
+        aGraph.eachNode(function(node) {
+               !animating && opt.onBeforePlotNode(node);
+               that.plotNode(node, canvas, animating);
+               !animating && opt.onAfterPlotNode(node);
+                       that.labels.plotLabel(canvas, node, opt);
+        });
+    },
+
+  /*
+      Plots a Subtree.
+   */
+   plotTree: function(node, opt, animating) {
+       var that = this, 
+           viz = this.viz, 
+           canvas = viz.canvas,
+           config = this.config,
+           ctx = canvas.getCtx();
+       var nodeAlpha = node.getData('alpha');
+       node.eachSubnode(function(elem) {
+         if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
+             var adj = node.getAdjacency(elem.id);
+             !animating && opt.onBeforePlotLine(adj);
+             that.plotLine(adj, canvas, animating);
+             !animating && opt.onAfterPlotLine(adj);
+             that.plotTree(elem, opt, animating);
+         }
+       });
+       if(node.drawn) {
+           !animating && opt.onBeforePlotNode(node);
+           this.plotNode(node, canvas, animating);
+           !animating && opt.onAfterPlotNode(node);
+           if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
+               this.labels.plotLabel(canvas, node, opt);
+           else 
+               this.labels.hideLabel(node, false);
+       } else {
+           this.labels.hideLabel(node, true);
+       }
+   },
+
+  /*
+       Method: plotNode
+    
+       Plots a <Graph.Node>.
+
+       Parameters:
+       
+       node - (object) A <Graph.Node>.
+       canvas - (object) A <Canvas> element.
+
+    */
+    plotNode: function(node, canvas, animating) {
+        var f = node.getData('type'), 
+            ctxObj = this.node.CanvasStyles;
+        if(f != 'none') {
+          var width = node.getData('lineWidth'),
+              color = node.getData('color'),
+              alpha = node.getData('alpha'),
+              ctx = canvas.getCtx();
+          ctx.save();
+          ctx.lineWidth = width;
+          ctx.fillStyle = ctx.strokeStyle = color;
+          ctx.globalAlpha = alpha;
+          
+          for(var s in ctxObj) {
+            ctx[s] = node.getCanvasStyle(s);
+          }
+
+          this.nodeTypes[f].render.call(this, node, canvas, animating);
+          ctx.restore();
+        }
+    },
+    
+    /*
+       Method: plotLine
+    
+       Plots a <Graph.Adjacence>.
+
+       Parameters:
+
+       adj - (object) A <Graph.Adjacence>.
+       canvas - (object) A <Canvas> instance.
+
+    */
+    plotLine: function(adj, canvas, animating) {
+      var f = adj.getData('type'),
+          ctxObj = this.edge.CanvasStyles;
+      if(f != 'none') {
+        var width = adj.getData('lineWidth'),
+            color = adj.getData('color'),
+            ctx = canvas.getCtx(),
+            nodeFrom = adj.nodeFrom,
+            nodeTo = adj.nodeTo;
+        
+        ctx.save();
+        ctx.lineWidth = width;
+        ctx.fillStyle = ctx.strokeStyle = color;
+        ctx.globalAlpha = Math.min(nodeFrom.getData('alpha'), 
+            nodeTo.getData('alpha'), 
+            adj.getData('alpha'));
+        
+        for(var s in ctxObj) {
+          ctx[s] = adj.getCanvasStyle(s);
+        }
+
+        this.edgeTypes[f].render.call(this, adj, 0, canvas, animating);
+        ctx.restore();
+      }
+    },
+
+       /*
+               Method: plotBezierLine
+
+               Plots a <MultiGraph.Adjacence>.
+
+               Parameters:
+               adj - (object) A <MultiGraph.Adjacence>.
+               pos - (int) Position of this line
+               total - (int) Total number of lines contained within this batch
+       */
+       plotBezierLine: function(adj, pos, total, canvas, animating) {
+                 var f = adj.getData('type'),
+          ctxObj = this.edge.CanvasStyles;
+                 if(f != 'none') {
+                       var width = adj.getData('lineWidth'),
+                               color = adj.getData('color'),
+                               ctx = canvas.getCtx(),
+                               nodeFrom = adj.nodeFrom,
+                               nodeTo = adj.nodeTo;
+                       
+                       ctx.save();
+                       ctx.lineWidth = width;
+                       ctx.fillStyle = ctx.strokeStyle = color;
+                       ctx.globalAlpha = Math.min(nodeFrom.getData('alpha'), 
+                               nodeTo.getData('alpha'), 
+                               adj.getData('alpha'));
+                       
+                       for(var s in ctxObj) {
+                         ctx[s] = adj.getCanvasStyle(s);
+                       }
+
+                       var alpha = parseInt(pos,10);
+                       var start = (0.5-(total/2));
+                       alpha += start;
+
+                       this.edgeTypes[f].render.call(this, adj, alpha, canvas, animating);
+                       ctx.restore();
+               }
+       }
+  
+};
+
+
+/*
+ * File: Graph.Label.js
+ *
+*/
+
+/*
+   Object: Graph.Label
+
+   An interface for plotting/hiding/showing labels.
+
+   Description:
+
+   This is a generic interface for plotting/hiding/showing labels.
+   The <Graph.Label> interface is implemented in multiple ways to provide
+   different label types.
+
+   For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
+   HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
+   The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
+   
+   All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
+*/
+
+MultiGraph.Label = {};
+
+/*
+   Class: Graph.Label.Native
+
+   Implements labels natively, using the Canvas text API.
+*/
+MultiGraph.Label.Native = new Class({
+    initialize: function(viz) {
+      this.viz = viz;
+    },
+
+    /*
+       Method: plotLabel
+
+       Plots a label for a given node.
+
+       Parameters:
+
+       canvas - (object) A <Canvas> instance.
+       node - (object) A <Graph.Node>.
+       controller - (object) A configuration object.
+       
+       Example:
+       
+       (start code js)
+       var viz = new $jit.Viz(options);
+       var node = viz.graph.getNode('nodeId');
+       viz.labels.plotLabel(viz.canvas, node, viz.config);
+       (end code)
+    */
+    plotLabel: function(canvas, node, controller) {
+      var ctx = canvas.getCtx();
+      var pos = node.pos.getc(true);
+
+      ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
+      ctx.textAlign = node.getLabelData('textAlign');
+      ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
+      ctx.textBaseline = node.getLabelData('textBaseline');
+
+      this.renderLabel(canvas, node, controller);
+    },
+
+    /*
+       renderLabel
+
+       Does the actual rendering of the label in the canvas. The default
+       implementation renders the label close to the position of the node, this
+       method should be overriden to position the labels differently.
+
+       Parameters:
+
+       canvas - A <Canvas> instance.
+       node - A <Graph.Node>.
+       controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
+    */
+    renderLabel: function(canvas, node, controller) {
+      var ctx = canvas.getCtx();
+      var pos = node.pos.getc(true);
+      ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
+    },
+
+    /*
+       Method: hideLabel
+   
+       Hides the corresponding <Graph.Node> label.
+    
+       Parameters:
+   
+       node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
+       show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
+   
+       Example:
+       (start code js)
+        var rg = new $jit.Viz(options);
+        viz.labels.hideLabel(viz.graph.getNode('someid'), false);
+       (end code)
+    */
+    hideLabel: function(node, show) {
+      node = $.splat(node);
+      var al = show ? false : true;
+      $.each(node, function(n) {
+        n._hideLabel = al;
+      });
+    },
+    hideLabels: $.empty
+});
+
+/*
+   Class: Graph.Label.DOM
+
+   Abstract Class implementing some DOM label methods.
+
+   Implemented by:
+
+   <Graph.Label.HTML> and <Graph.Label.SVG>.
+
+*/
+MultiGraph.Label.DOM = new Class({
+    //A flag value indicating if node labels are being displayed or not.
+    labelsHidden: false,
+    //Label container
+    labelContainer: false,
+    //Label elements hash.
+    labels: {},
+
+    /*
+       Method: getLabelContainer
+
+       Lazy fetcher for the label container.
+
+       Returns:
+
+       The label container DOM element.
+
+       Example:
+
+      (start code js)
+        var viz = new $jit.Viz(options);
+        var labelContainer = viz.labels.getLabelContainer();
+        alert(labelContainer.innerHTML);
+      (end code)
+    */
+    getLabelContainer: function() {
+      return this.labelContainer ?
+        this.labelContainer :
+        this.labelContainer = document.getElementById(this.viz.config.labelContainer);
+    },
+
+    /*
+       Method: getLabel
+
+       Lazy fetcher for the label element.
+
+       Parameters:
+
+       id - (string) The label id (which is also a <Graph.Node> id).
+
+       Returns:
+
+       The label element.
+
+       Example:
+
+      (start code js)
+        var viz = new $jit.Viz(options);
+        var label = viz.labels.getLabel('someid');
+        alert(label.innerHTML);
+      (end code)
+
+    */
+    getLabel: function(id) {
+      return (id in this.labels && this.labels[id] != null) ?
+        this.labels[id] :
+        this.labels[id] = document.getElementById(id);
+    },
+
+    /*
+       Method: hideLabels
+
+       Hides all labels (by hiding the label container).
+
+       Parameters:
+
+       hide - (boolean) A boolean value indicating if the label container must be hidden or not.
+
+       Example:
+       (start code js)
+        var viz = new $jit.Viz(options);
+        rg.labels.hideLabels(true);
+       (end code)
+
+    */
+    hideLabels: function (hide) {
+      var container = this.getLabelContainer();
+      if(hide)
+        container.style.display = 'none';
+      else
+        container.style.display = '';
+      this.labelsHidden = hide;
+    },
+
+    /*
+       Method: clearLabels
+
+       Clears the label container.
+
+       Useful when using a new visualization with the same canvas element/widget.
+
+       Parameters:
+
+       force - (boolean) Forces deletion of all labels.
+
+       Example:
+       (start code js)
+        var viz = new $jit.Viz(options);
+        viz.labels.clearLabels();
+        (end code)
+    */
+    clearLabels: function(force) {
+      for(var id in this.labels) {
+        if (force || !this.viz.graph.hasNode(id)) {
+          this.disposeLabel(id);
+          delete this.labels[id];
+        }
+      }
+    },
+
+    /*
+       Method: disposeLabel
+
+       Removes a label.
+
+       Parameters:
+
+       id - (string) A label id (which generally is also a <Graph.Node> id).
+
+       Example:
+       (start code js)
+        var viz = new $jit.Viz(options);
+        viz.labels.disposeLabel('labelid');
+       (end code)
+    */
+    disposeLabel: function(id) {
+      var elem = this.getLabel(id);
+      if(elem && elem.parentNode) {
+        elem.parentNode.removeChild(elem);
+      }
+    },
+
+    /*
+       Method: hideLabel
+
+       Hides the corresponding <Graph.Node> label.
+
+       Parameters:
+
+       node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
+       show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
+
+       Example:
+       (start code js)
+        var rg = new $jit.Viz(options);
+        viz.labels.hideLabel(viz.graph.getNode('someid'), false);
+       (end code)
+    */
+    hideLabel: function(node, show) {
+      node = $.splat(node);
+      var st = show ? "" : "none", lab, that = this;
+      $.each(node, function(n) {
+        lab = that.getLabel(n.id);
+        if (lab) {
+          lab.style.display = st;
+        }
+      });
+    },
+
+    /*
+       fitsInCanvas
+
+       Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
+
+       Parameters:
+
+       pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
+       canvas - A <Canvas> instance.
+
+       Returns:
+
+       A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
+
+    */
+    fitsInCanvas: function(pos, canvas) {
+      var size = canvas.getSize();
+      if (pos.w && pos.h) {
+       if (pos.x >= size.width || pos.x < -pos.w
+         || pos.y >= size.height || pos.y < -pos.h) return false;              
+      }
+      else {
+       if (pos.x >= size.width || pos.x < 0
+         || pos.y >= size.height || pos.y < 0) return false;
+      }
+       return true;
+    }
+});
+
+/*
+   Class: Graph.Label.HTML
+
+   Implements HTML labels.
+
+   Extends:
+
+   All <Graph.Label.DOM> methods.
+
+*/
+MultiGraph.Label.HTML = new Class({
+    Implements: MultiGraph.Label.DOM,
+
+    /*
+       Method: plotLabel
+
+       Plots a label for a given node.
+
+       Parameters:
+
+       canvas - (object) A <Canvas> instance.
+       node - (object) A <Graph.Node>.
+       controller - (object) A configuration object.
+       
+      Example:
+       
+       (start code js)
+       var viz = new $jit.Viz(options);
+       var node = viz.graph.getNode('nodeId');
+       viz.labels.plotLabel(viz.canvas, node, viz.config);
+       (end code)
+
+
+    */
+    plotLabel: function(canvas, node, controller) {
+      var id = node.id, tag = this.getLabel(id);
+
+      if(!tag && !(tag = document.getElementById(id))) {
+        tag = document.createElement('div');
+        var container = this.getLabelContainer();
+        tag.id = id;
+        tag.className = 'node';
+        tag.style.position = 'absolute';
+        controller.onCreateLabel(tag, node);
+        container.appendChild(tag);
+        this.labels[node.id] = tag;
+      }
+
+      this.placeLabel(tag, node, controller);
+    }
+});
+
+/*
+   Class: Graph.Label.SVG
+
+   Implements SVG labels.
+
+   Extends:
+
+   All <Graph.Label.DOM> methods.
+*/
+MultiGraph.Label.SVG = new Class({
+    Implements: MultiGraph.Label.DOM,
+
+    /*
+       Method: plotLabel
+
+       Plots a label for a given node.
+
+       Parameters:
+
+       canvas - (object) A <Canvas> instance.
+       node - (object) A <Graph.Node>.
+       controller - (object) A configuration object.
+       
+       Example:
+       
+       (start code js)
+       var viz = new $jit.Viz(options);
+       var node = viz.graph.getNode('nodeId');
+       viz.labels.plotLabel(viz.canvas, node, viz.config);
+       (end code)
+
+
+    */
+    plotLabel: function(canvas, node, controller) {
+      var id = node.id, tag = this.getLabel(id);
+      if(!tag && !(tag = document.getElementById(id))) {
+        var ns = 'http://www.w3.org/2000/svg';
+          tag = document.createElementNS(ns, 'svg:text');
+        var tspan = document.createElementNS(ns, 'svg:tspan');
+        tag.appendChild(tspan);
+        var container = this.getLabelContainer();
+        tag.setAttribute('id', id);
+        tag.setAttribute('class', 'node');
+        container.appendChild(tag);
+        controller.onCreateLabel(tag, node);
+        this.labels[node.id] = tag;
+      }
+      this.placeLabel(tag, node, controller);
+    }
+});
+
+
+
+/*
+ * File: MultiLoader.js
+ * 
+ */
+
+/*
+   Object: MultiLoader
+
+   Provides methods for loading and serving JSON data.
+*/
+var MultiLoader = {
+     construct: function(json) {
+        var ans = new MultiGraph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
+               //make graph
+               (function (ans, json) {
+                       var getNode = function(id) {
+                         for(var i=0, l=json.length; i<l; i++) {
+                               if(json[i].id == id) {
+                                 return json[i];
+                               }
+                         }
+                         // The node was not defined in the JSON
+                         // Let's create it
+                         var newNode = {
+                                       "id" : id,
+                                       "name" : id
+                               };
+                         return ans.addNode(newNode);
+                       };
+
+                       for(var i=0, l=json.length; i<l; i++) {
+                         ans.addNode(json[i]);
+                         var adj = json[i].adjacencies;
+                         if (adj) {
+                               for(var j=0, lj=adj.length; j<lj; j++) {
+                                 var node = adj[j], data = {};
+                                 data = $.merge(node.data, {});
+                                 node = node.nodeTo;
+                                 ans.addAdjacence(json[i], getNode(node), data);
+                               }
+                         }
+                       }
+               })(ans, json);
+
+        return ans;
+    },
+
+    /*
+     Method: loadJSON
+    
+     Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
+     
+      A JSON tree or graph structure consists of nodes, each having as properties
+       
+       id - (string) A unique identifier for the node
+       name - (string) A node's name
+       data - (object) The data optional property contains a hash (i.e {}) 
+       where you can store all the information you want about this node.
+        
+      For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
+      
+      Example:
+
+      (start code js)
+        var json = {  
+          "id": "aUniqueIdentifier",  
+          "name": "usually a nodes name",  
+          "data": {
+            "some key": "some value",
+            "some other key": "some other value"
+           },  
+          "children": [ *other nodes or empty* ]  
+        };  
+      (end code)
+        
+        JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
+        For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
+        
+        There are two types of *Graph* structures, *simple* and *extended* graph structures.
+        
+        For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
+        id of the node connected to the main node.
+        
+        Example:
+        
+        (start code js)
+        var json = [  
+          {  
+            "id": "aUniqueIdentifier",  
+            "name": "usually a nodes name",  
+            "data": {
+              "some key": "some value",
+              "some other key": "some other value"
+             },  
+            "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
+          },
+
+          'other nodes go here...' 
+        ];          
+        (end code)
+        
+        For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
+        
+        nodeTo - (string) The other node connected by this adjacency.
+        data - (object) A data property, where we can store custom key/value information.
+        
+        Example:
+        
+        (start code js)
+        var json = [  
+          {  
+            "id": "aUniqueIdentifier",  
+            "name": "usually a nodes name",  
+            "data": {
+              "some key": "some value",
+              "some other key": "some other value"
+             },  
+            "adjacencies": [  
+            {  
+              nodeTo:"aNodeId",  
+              data: {} //put whatever you want here  
+            },
+            'other adjacencies go here...'  
+          },
+
+          'other nodes go here...' 
+        ];          
+        (end code)
+       
+       About the data property:
+       
+       As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
+       You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
+       have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
+       
+       For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
+       <Options.Node> will override the general value for that option with that particular value. For this to work 
+       however, you do have to set *overridable = true* in <Options.Node>.
+       
+       The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
+       if <Options.Edge> has *overridable = true*.
+       
+       When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
+       since this is the value which will be taken into account when creating the layout. 
+       The same thing goes for the *$color* parameter.
+       
+       In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
+       *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
+       canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
+       to the *shadowBlur* property.
+       
+       These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
+       by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
+       
+       Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
+       information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
+       
+       loadJSON Parameters:
+    
+        json - A JSON Tree or Graph structure.
+        i - For Graph structures only. Sets the indexed node as root for the visualization.
+
+    */
+    loadJSON: function(json, i) {
+      this.json = json;
+      //if they're canvas labels erase them.
+      if(this.labels && this.labels.clearLabels) {
+        this.labels.clearLabels(true);
+      }
+      this.graph = this.construct(json);
+      if($.type(json) != 'array'){
+        this.root = json.id;
+      } else {
+        this.root = json[i? i : 0].id;
+      }
+    },
+    
+    /*
+      Method: toJSON
+   
+      Returns a JSON tree/graph structure from the visualization's <Graph>. 
+      See <MultiLoader.loadJSON> for the graph formats available.
+      
+      See also:
+      
+      <MultiLoader.loadJSON>
+      
+      Parameters:
+      
+      type - (string) Default's "tree". The type of the JSON structure to be returned. 
+      Possible options are "tree" or "graph".
+    */    
+    toJSON: function(type) {
+      type = type || "tree";
+      if(type == 'tree') {
+        var ans = {};
+        var rootNode = this.graph.getNode(this.root);
+        var ans = (function recTree(node) {
+          var ans = {};
+          ans.id = node.id;
+          ans.name = node.name;
+          ans.data = node.data;
+          var ch =[];
+          node.eachSubnode(function(n) {
+            ch.push(recTree(n));
+          });
+          ans.children = ch;
+          return ans;
+        })(rootNode);
+        return ans;
+      } else {
+        var ans = [];
+        var T = !!this.graph.getNode(this.root).visited;
+        this.graph.eachNode(function(node) {
+          var ansNode = {};
+          ansNode.id = node.id;
+          ansNode.name = node.name;
+          ansNode.data = node.data;
+          var adjs = [];
+          node.eachAdjacency(function(adj) {
+            var nodeTo = adj.nodeTo;
+            if(!!nodeTo.visited === T) {
+              var ansAdj = {};
+              ansAdj.nodeTo = nodeTo.id;
+              ansAdj.data = adj.data;
+              adjs.push(ansAdj);
+            }
+          });
+          ansNode.adjacencies = adjs;
+          ans.push(ansNode);
+          node.visited = !T;
+        });
+        return ans;
+      }
+    }
+};
+
+
+
+/*
+ * File: Graph.js
+ *
+*/
+
+/*
+ Class: Graph
+
+ A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
+
+ An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
+ Example:
+
+ (start code js)
+   //create new visualization
+   var viz = new $jit.Viz(options);
+   //load JSON data
+   viz.loadJSON(json);
+   //access model
+   viz.graph; //<Graph> instance
+ (end code)
+ Implements:
+ The following <Graph.Util> methods are implemented in <Graph>
+  - <Graph.Util.getNode>
+  - <Graph.Util.eachNode>
+  - <Graph.Util.computeLevels>
+  - <Graph.Util.eachBFS>
+  - <Graph.Util.clean>
+  - <Graph.Util.getClosestNodeToPos>
+  - <Graph.Util.getClosestNodeToOrigin>
+*/  
+
+$jit.Graph = new Class({
+
+  initialize: function(opt, Node, Edge, Label) {
+    var innerOptions = {
+    'klass': Complex,
+    'Node': {}
+    };
+    this.Node = Node;
+    this.Edge = Edge;
+    this.Label = Label;
+    this.opt = $.merge(innerOptions, opt || {});
+    this.nodes = {};
+    this.edges = {};
+    
+    //add nodeList methods
+    var that = this;
+    this.nodeList = {};
+    for(var p in Accessors) {
+      that.nodeList[p] = (function(p) {
+        return function() {
+          var args = Array.prototype.slice.call(arguments);
+          that.eachNode(function(n) {
+            n[p].apply(n, args);
+          });
+        };
+      })(p);
+    }
+
+ },
+
+/*
+     Method: getNode
+    
+     Returns a <Graph.Node> by *id*.
+
+     Parameters:
+
+     id - (string) A <Graph.Node> id.
+
+     Example:
+
+     (start code js)
+       var node = graph.getNode('nodeId');
+     (end code)
+*/  
+ getNode: function(id) {
+    if(this.hasNode(id)) return this.nodes[id];
+    return false;
+ },
+
+ /*
+     Method: get
+    
+     An alias for <Graph.Util.getNode>. Returns a node by *id*.
+    
+     Parameters:
+    
+     id - (string) A <Graph.Node> id.
+    
+     Example:
+    
+     (start code js)
+       var node = graph.get('nodeId');
+     (end code)
+*/  
+  get: function(id) {
+    return this.getNode(id);
+  },
+
+ /*
+   Method: getByName
+  
+   Returns a <Graph.Node> by *name*.
+  
+   Parameters:
+  
+   name - (string) A <Graph.Node> name.
+  
+   Example:
+  
+   (start code js)
+     var node = graph.getByName('someName');
+   (end code)
+  */  
+  getByName: function(name) {
+    for(var id in this.nodes) {
+      var n = this.nodes[id];
+      if(n.name == name) return n;
+    }
+    return false;
+  },
+
+/*
+   Method: getAdjacence
+  
+   Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
+
+   Parameters:
+
+   id - (string) A <Graph.Node> id.
+   id2 - (string) A <Graph.Node> id.
+*/  
+  getAdjacence: function (id, id2) {
+    if(id in this.edges) {
+      return this.edges[id][id2];
+    }
+    return false;
+ },
+
+    /*
+     Method: addNode
+    
+     Adds a node.
+     
+     Parameters:
+    
+      obj - An object with the properties described below
+
+      id - (string) A node id
+      name - (string) A node's name
+      data - (object) A node's data hash
+
+    See also:
+    <Graph.Node>
+
+  */  
+  addNode: function(obj) { 
+   if(!this.nodes[obj.id]) {  
+     var edges = this.edges[obj.id] = {};
+     this.nodes[obj.id] = new Graph.Node($.extend({
+        'id': obj.id,
+        'name': obj.name,
+        'data': $.merge(obj.data || {}, {}),
+        'adjacencies': edges 
+      }, this.opt.Node), 
+      this.opt.klass, 
+      this.Node, 
+      this.Edge,
+      this.Label);
+    }
+    return this.nodes[obj.id];
+  },
+  
+    /*
+     Method: addAdjacence
+    
+     Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
+     
+     Parameters:
+    
+      obj - (object) A <Graph.Node> object.
+      obj2 - (object) Another <Graph.Node> object.
+      data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
+
+    See also:
+
+    <Graph.Node>, <Graph.Adjacence>
+    */  
+  addAdjacence: function (obj, obj2, data) {
+    if(!this.hasNode(obj.id)) { this.addNode(obj); }
+    if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
+    obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
+    if(!obj.adjacentTo(obj2)) {
+      var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
+      var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
+      adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
+      return adjsObj[obj2.id];
+    }
+    return this.edges[obj.id][obj2.id];
+ },
+
+    /*
+     Method: removeNode
+    
+     Removes a <Graph.Node> matching the specified *id*.
+
+     Parameters:
+
+     id - (string) A node's id.
+
+    */  
+  removeNode: function(id) {
+    if(this.hasNode(id)) {
+      delete this.nodes[id];
+      var adjs = this.edges[id];
+      for(var to in adjs) {
+        delete this.edges[to][id];
+      }
+      delete this.edges[id];
+    }
+  },
+  
+/*
+     Method: removeAdjacence
+    
+     Removes a <Graph.Adjacence> matching *id1* and *id2*.
+
+     Parameters:
+
+     id1 - (string) A <Graph.Node> id.
+     id2 - (string) A <Graph.Node> id.
+*/  
+  removeAdjacence: function(id1, id2) {
+    delete this.edges[id1][id2];
+    delete this.edges[id2][id1];
+  },
+
+   /*
+     Method: hasNode
+    
+     Returns a boolean indicating if the node belongs to the <Graph> or not.
+     
+     Parameters:
+    
+        id - (string) Node id.
+   */  
+  hasNode: function(id) {
+    return id in this.nodes;
+  },
+  
+  /*
+    Method: empty
+
+    Empties the Graph
+
+  */
+  empty: function() { this.nodes = {}; this.edges = {};}
+
+});
+
+var Graph = $jit.Graph;
+
+/*
+ Object: Accessors
+ Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
+ */
+var Accessors;
+
+(function () {
+  var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
+    var data;
+    type = type || 'current';
+    prefix = "$" + (prefix ? prefix + "-" : "");
+
+    if(type == 'current') {
+      data = this.data;
+    } else if(type == 'start') {
+      data = this.startData;
+    } else if(type == 'end') {
+      data = this.endData;
+    }
+
+    var dollar = prefix + prop;
+
+    if(force) {
+      return data[dollar];
+    }
+
+    if(!this.Config.overridable)
+      return prefixConfig[prop] || 0;
+
+    return (dollar in data) ?
+      data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
+  }
+
+  var setDataInternal = function(prefix, prop, value, type) {
+    type = type || 'current';
+    prefix = '$' + (prefix ? prefix + '-' : '');
+
+    var data;
+
+    if(type == 'current') {
+      data = this.data;
+    } else if(type == 'start') {
+      data = this.startData;
+    } else if(type == 'end') {
+      data = this.endData;
+    }
+
+    data[prefix + prop] = value;
+  }
+
+  var removeDataInternal = function(prefix, properties) {
+    prefix = '$' + (prefix ? prefix + '-' : '');
+    var that = this;
+    $.each(properties, function(prop) {
+      var pref = prefix + prop;
+      delete that.data[pref];
+      delete that.endData[pref];
+      delete that.startData[pref];
+    });
+  }
+
+  Accessors = {
+    /*
+    Method: getData
+
+    Returns the specified data value property.
+    This is useful for querying special/reserved <Graph.Node> data properties
+    (i.e dollar prefixed properties).
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign is not needed. For
+              example *getData(width)* will return *data.$width*.
+      type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
+              data properties also. These properties are used when making animations.
+      force - (boolean) Whether to obtain the true value of the property (equivalent to
+              *data.$prop*) or to check for *node.overridable = true* first.
+
+    Returns:
+
+      The value of the dollar prefixed property or the global Node/Edge property
+      value if *overridable=false*
+
+    Example:
+    (start code js)
+     node.getData('width'); //will return node.data.$width if Node.overridable=true;
+    (end code)
+    */
+    getData: function(prop, type, force) {
+      return getDataInternal.call(this, "", prop, type, force, this.Config);
+    },
+
+
+    /*
+    Method: setData
+
+    Sets the current data property with some specific value.
+    This method is only useful for reserved (dollar prefixed) properties.
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign is not necessary. For
+              example *setData(width)* will set *data.$width*.
+      value - (mixed) The value to store.
+      type  - (string) The type of the data property to store. Default's "current" but
+              can also be "start" or "end".
+
+    Example:
+    
+    (start code js)
+     node.setData('width', 30);
+    (end code)
+    
+    If we were to make an animation of a node/edge width then we could do
+    
+    (start code js)
+      var node = viz.getNode('nodeId');
+      //set start and end values
+      node.setData('width', 10, 'start');
+      node.setData('width', 30, 'end');
+      //will animate nodes width property
+      viz.fx.animate({
+        modes: ['node-property:width'],
+        duration: 1000
+      });
+    (end code)
+    */
+    setData: function(prop, value, type) {
+      setDataInternal.call(this, "", prop, value, type);
+    },
+
+    /*
+    Method: setDataset
+
+    Convenience method to set multiple data values at once.
+    
+    Parameters:
+    
+    types - (array|string) A set of 'current', 'end' or 'start' values.
+    obj - (object) A hash containing the names and values of the properties to be altered.
+
+    Example:
+    (start code js)
+      node.setDataset(['current', 'end'], {
+        'width': [100, 5],
+        'color': ['#fff', '#ccc']
+      });
+      //...or also
+      node.setDataset('end', {
+        'width': 5,
+        'color': '#ccc'
+      });
+    (end code)
+    
+    See also: 
+    
+    <Accessors.setData>
+    
+    */
+    setDataset: function(types, obj) {
+      types = $.splat(types);
+      for(var attr in obj) {
+        for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+          this.setData(attr, val[i], types[i]);
+        }
+      }
+    },
+    
+    /*
+    Method: removeData
+
+    Remove data properties.
+
+    Parameters:
+
+    One or more property names as arguments. The dollar sign is not needed.
+
+    Example:
+    (start code js)
+    node.removeData('width'); //now the default width value is returned
+    (end code)
+    */
+    removeData: function() {
+      removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
+    },
+
+    /*
+    Method: getCanvasStyle
+
+    Returns the specified canvas style data value property. This is useful for
+    querying special/reserved <Graph.Node> canvas style data properties (i.e.
+    dollar prefixed properties that match with $canvas-<name of canvas style>).
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign is not needed. For
+              example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
+      type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
+              data properties also.
+              
+    Example:
+    (start code js)
+      node.getCanvasStyle('shadowBlur');
+    (end code)
+    
+    See also:
+    
+    <Accessors.getData>
+    */
+    getCanvasStyle: function(prop, type, force) {
+      return getDataInternal.call(
+          this, 'canvas', prop, type, force, this.Config.CanvasStyles);
+    },
+
+    /*
+    Method: setCanvasStyle
+
+    Sets the canvas style data property with some specific value.
+    This method is only useful for reserved (dollar prefixed) properties.
+    
+    Parameters:
+    
+    prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
+    value - (mixed) The value to set to the property.
+    type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
+    
+    Example:
+    
+    (start code js)
+     node.setCanvasStyle('shadowBlur', 30);
+    (end code)
+    
+    If we were to make an animation of a node/edge shadowBlur canvas style then we could do
+    
+    (start code js)
+      var node = viz.getNode('nodeId');
+      //set start and end values
+      node.setCanvasStyle('shadowBlur', 10, 'start');
+      node.setCanvasStyle('shadowBlur', 30, 'end');
+      //will animate nodes canvas style property for nodes
+      viz.fx.animate({
+        modes: ['node-style:shadowBlur'],
+        duration: 1000
+      });
+    (end code)
+    
+    See also:
+    
+    <Accessors.setData>.
+    */
+    setCanvasStyle: function(prop, value, type) {
+      setDataInternal.call(this, 'canvas', prop, value, type);
+    },
+
+    /*
+    Method: setCanvasStyles
+
+    Convenience method to set multiple styles at once.
+
+    Parameters:
+    
+    types - (array|string) A set of 'current', 'end' or 'start' values.
+    obj - (object) A hash containing the names and values of the properties to be altered.
+
+    See also:
+    
+    <Accessors.setDataset>.
+    */
+    setCanvasStyles: function(types, obj) {
+      types = $.splat(types);
+      for(var attr in obj) {
+        for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+          this.setCanvasStyle(attr, val[i], types[i]);
+        }
+      }
+    },
+
+    /*
+    Method: removeCanvasStyle
+
+    Remove canvas style properties from data.
+
+    Parameters:
+    
+    A variable number of canvas style strings.
+
+    See also:
+    
+    <Accessors.removeData>.
+    */
+    removeCanvasStyle: function() {
+      removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
+    },
+
+    /*
+    Method: getLabelData
+
+    Returns the specified label data value property. This is useful for
+    querying special/reserved <Graph.Node> label options (i.e.
+    dollar prefixed properties that match with $label-<name of label style>).
+
+    Parameters:
+
+      prop  - (string) The name of the property. The dollar sign prefix is not needed. For
+              example *getLabelData(size)* will return *data[$label-size]*.
+      type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
+              data properties also.
+              
+    See also:
+    
+    <Accessors.getData>.
+    */
+    getLabelData: function(prop, type, force) {
+      return getDataInternal.call(
+          this, 'label', prop, type, force, this.Label);
+    },
+
+    /*
+    Method: setLabelData
+
+    Sets the current label data with some specific value.
+    This method is only useful for reserved (dollar prefixed) properties.
+
+    Parameters:
+    
+    prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
+    value - (mixed) The value to set to the property.
+    type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
+    
+    Example:
+    
+    (start code js)
+     node.setLabelData('size', 30);
+    (end code)
+    
+    If we were to make an animation of a node label size then we could do
+    
+    (start code js)
+      var node = viz.getNode('nodeId');
+      //set start and end values
+      node.setLabelData('size', 10, 'start');
+      node.setLabelData('size', 30, 'end');
+      //will animate nodes label size
+      viz.fx.animate({
+        modes: ['label-property:size'],
+        duration: 1000
+      });
+    (end code)
+    
+    See also:
+    
+    <Accessors.setData>.
+    */
+    setLabelData: function(prop, value, type) {
+      setDataInternal.call(this, 'label', prop, value, type);
+    },
+
+    /*
+    Method: setLabelDataset
+
+    Convenience function to set multiple label data at once.
+
+    Parameters:
+    
+    types - (array|string) A set of 'current', 'end' or 'start' values.
+    obj - (object) A hash containing the names and values of the properties to be altered.
+
+    See also:
+    
+    <Accessors.setDataset>.
+    */
+    setLabelDataset: function(types, obj) {
+      types = $.splat(types);
+      for(var attr in obj) {
+        for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+          this.setLabelData(attr, val[i], types[i]);
+        }
+      }
+    },
+
+    /*
+    Method: removeLabelData
+
+    Remove label properties from data.
+    
+    Parameters:
+    
+    A variable number of label property strings.
+
+    See also:
+    
+    <Accessors.removeData>.
+    */
+    removeLabelData: function() {
+      removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
+    }
+  };
+})();
+
+/*
+     Class: Graph.Node
+
+     A <Graph> node.
+     
+     Implements:
+     
+     <Accessors> methods.
+     
+     The following <Graph.Util> methods are implemented by <Graph.Node>
+     
+    - <Graph.Util.eachAdjacency>
+    - <Graph.Util.eachLevel>
+    - <Graph.Util.eachSubgraph>
+    - <Graph.Util.eachSubnode>
+    - <Graph.Util.anySubnode>
+    - <Graph.Util.getSubnodes>
+    - <Graph.Util.getParents>
+    - <Graph.Util.isDescendantOf>     
+*/
+Graph.Node = new Class({
+    
+  initialize: function(opt, klass, Node, Edge, Label) {
+    var innerOptions = {
+      'id': '',
+      'name': '',
+      'data': {},
+      'startData': {},
+      'endData': {},
+      'adjacencies': {},
+
+      'selected': false,
+      'drawn': false,
+      'exist': false,
+
+      'angleSpan': {
+        'begin': 0,
+        'end' : 0
+      },
+
+      'pos': new klass,
+      'startPos': new klass,
+      'endPos': new klass
+    };
+    
+    $.extend(this, $.extend(innerOptions, opt));
+    this.Config = this.Node = Node;
+    this.Edge = Edge;
+    this.Label = Label;
+  },
+
+    /*
+       Method: adjacentTo
+    
+       Indicates if the node is adjacent to the node specified by id
+
+       Parameters:
+    
+          id - (string) A node id.
+    
+       Example:
+       (start code js)
+        node.adjacentTo('nodeId') == true;
+       (end code)
+    */
+    adjacentTo: function(node) {
+        return node.id in this.adjacencies;
+    },
+
+    /*
+     Method: adjacentWithDirectionTo
+
+     Indicates if the node has a directed edge to the node specified by id
+
+     Parameters:
+
+     id - (string) A node id.
+
+     Example:
+     (start code js)
+     node.adjacentWithDirectionTo('nodeId') == true;
+     (end code)
+     */
+    adjacentWithDirectionTo: function(node) {
+        var areNeighbors = node.id in this.adjacencies;
+        if (!areNeighbors) {
+            return false;
+        }
+
+        var direction = this.adjacencies[node.id].data.$direction;
+        return direction[0] === this.id ;
+    },
+
+    /*
+       Method: getAdjacency
+    
+       Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
+
+       Parameters:
+    
+          id - (string) A node id.
+    */  
+    getAdjacency: function(id) {
+        return this.adjacencies[id];
+    },
+
+    /*
+      Method: getPos
+   
+      Returns the position of the node.
+  
+      Parameters:
+   
+         type - (string) Default's *current*. Possible values are "start", "end" or "current".
+   
+      Returns:
+   
+        A <Complex> or <Polar> instance.
+  
+      Example:
+      (start code js)
+       var pos = node.getPos('end');
+      (end code)
+   */
+   getPos: function(type) {
+       type = type || "current";
+       if(type == "current") {
+         return this.pos;
+       } else if(type == "end") {
+         return this.endPos;
+       } else if(type == "start") {
+         return this.startPos;
+       }
+   },
+   /*
+     Method: setPos
+  
+     Sets the node's position.
+  
+     Parameters:
+  
+        value - (object) A <Complex> or <Polar> instance.
+        type - (string) Default's *current*. Possible values are "start", "end" or "current".
+  
+     Example:
+     (start code js)
+      node.setPos(new $jit.Complex(0, 0), 'end');
+     (end code)
+  */
+  setPos: function(value, type) {
+      type = type || "current";
+      var pos;
+      if(type == "current") {
+        pos = this.pos;
+      } else if(type == "end") {
+        pos = this.endPos;
+      } else if(type == "start") {
+        pos = this.startPos;
+      }
+      pos.set(value);
+  }
+});
+
+Graph.Node.implement(Accessors);
+
+/*
+     Class: Graph.Adjacence
+
+     A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
+     
+     Implements:
+     
+     <Accessors> methods.
+
+     See also:
+
+     <Graph>, <Graph.Node>
+
+     Properties:
+     
+      nodeFrom - A <Graph.Node> connected by this edge.
+      nodeTo - Another  <Graph.Node> connected by this edge.
+      data - Node data property containing a hash (i.e {}) with custom options.
+*/
+Graph.Adjacence = new Class({
+  
+  initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
+    this.nodeFrom = nodeFrom;
+    this.nodeTo = nodeTo;
+    this.data = data || {};
+    this.startData = {};
+    this.endData = {};
+    this.Config = this.Edge = Edge;
+    this.Label = Label;
+  }
+});
+
+Graph.Adjacence.implement(Accessors);
+
+/*
+   Object: Graph.Util
+
+   <Graph> traversal and processing utility object.
+   
+   Note:
+   
+   For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
+*/
+Graph.Util = {
+    /*
+       filter
+    
+       For internal use only. Provides a filtering function based on flags.
+    */
+    filter: function(param) {
+        if(!param || !($.type(param) == 'string')) return function() { return true; };
+        var props = param.split(" ");
+        return function(elem) {
+            for(var i=0; i<props.length; i++) { 
+              if(elem[props[i]]) { 
+                return false; 
+              }
+            }
+            return true;
+        };
+    },
+    /*
+       Method: getNode
+    
+       Returns a <Graph.Node> by *id*.
+       
+       Also implemented by:
+       
+       <Graph>
+
+       Parameters:
+
+       graph - (object) A <Graph> instance.
+       id - (string) A <Graph.Node> id.
+
+       Example:
+
+       (start code js)
+         $jit.Graph.Util.getNode(graph, 'nodeid');
+         //or...
+         graph.getNode('nodeid');
+       (end code)
+    */
+    getNode: function(graph, id) {
+        return graph.nodes[id];
+    },
+    
+    /*
+       Method: eachNode
+    
+       Iterates over <Graph> nodes performing an *action*.
+       
+       Also implemented by:
+       
+       <Graph>.
+
+       Parameters:
+
+       graph - (object) A <Graph> instance.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachNode(graph, function(node) {
+          alert(node.name);
+         });
+         //or...
+         graph.eachNode(function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachNode: function(graph, action, flags) {
+        var filter = this.filter(flags);
+        for(var i in graph.nodes) {
+          if(filter(graph.nodes[i])) action(graph.nodes[i]);
+        } 
+    },
+    
+    /*
+      Method: each
+   
+      Iterates over <Graph> nodes performing an *action*. It's an alias for <Graph.Util.eachNode>.
+      
+      Also implemented by:
+      
+      <Graph>.
+  
+      Parameters:
+  
+      graph - (object) A <Graph> instance.
+      action - (function) A callback function having a <Graph.Node> as first formal parameter.
+  
+      Example:
+      (start code js)
+        $jit.Graph.Util.each(graph, function(node) {
+         alert(node.name);
+        });
+        //or...
+        graph.each(function(node) {
+          alert(node.name);
+        });
+      (end code)
+   */
+   each: function(graph, action, flags) {
+      this.eachNode(graph, action, flags); 
+   },
+
+ /*
+       Method: eachAdjacency
+    
+       Iterates over <Graph.Node> adjacencies applying the *action* function.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+
+       node - (object) A <Graph.Node>.
+       action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachAdjacency(node, function(adj) {
+          alert(adj.nodeTo.name);
+         });
+         //or...
+         node.eachAdjacency(function(adj) {
+           alert(adj.nodeTo.name);
+         });
+       (end code)
+    */
+    eachAdjacency: function(node, action, flags) {
+        var adj = node.adjacencies, filter = this.filter(flags);
+        for(var id in adj) {
+          var a = adj[id];
+          if(filter(a)) {
+            if(a.nodeFrom != node) {
+              var tmp = a.nodeFrom;
+              a.nodeFrom = a.nodeTo;
+              a.nodeTo = tmp;
+            }
+            action(a, id);
+          }
+        }
+    },
+
+     /*
+       Method: computeLevels
+    
+       Performs a BFS traversal setting the correct depth for each node.
+        
+       Also implemented by:
+       
+       <Graph>.
+       
+       Note:
+       
+       The depth of each node can then be accessed by 
+       >node._depth
+
+       Parameters:
+
+       graph - (object) A <Graph>.
+       id - (string) A starting node id for the BFS traversal.
+       startDepth - (optional|number) A minimum depth value. Default's 0.
+
+    */
+    computeLevels: function(graph, id, startDepth, flags) {
+        startDepth = startDepth || 0;
+        var filter = this.filter(flags);
+        this.eachNode(graph, function(elem) {
+            elem._flag = false;
+            elem._depth = -1;
+        }, flags);
+        var root = graph.getNode(id);
+        root._depth = startDepth;
+        var queue = [root];
+        while(queue.length != 0) {
+            var node = queue.pop();
+            node._flag = true;
+            this.eachAdjacency(node, function(adj) {
+                var n = adj.nodeTo;
+                if(n._flag == false && filter(n) && !adj._hiding) {
+                    if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
+                    queue.unshift(n);
+                }
+            }, flags);
+        }
+    },
+
+    /*
+       Method: eachBFS
+    
+       Performs a BFS traversal applying *action* to each <Graph.Node>.
+       
+       Also implemented by:
+       
+       <Graph>.
+
+       Parameters:
+
+       graph - (object) A <Graph>.
+       id - (string) A starting node id for the BFS traversal.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
+          alert(node.name);
+         });
+         //or...
+         graph.eachBFS('mynodeid', function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachBFS: function(graph, id, action, flags) {
+        var filter = this.filter(flags);
+        this.clean(graph);
+        var queue = [graph.getNode(id)];
+        while(queue.length != 0) {
+            var node = queue.pop();
+            if (!node) return;
+            node._flag = true;
+            action(node, node._depth);
+            this.eachAdjacency(node, function(adj) {
+                var n = adj.nodeTo;
+                if(n._flag == false && filter(n) && !adj._hiding) {
+                    n._flag = true;
+                    queue.unshift(n);
+                }
+            }, flags);
+        }
+    },
+    
+    /*
+       Method: eachLevel
+    
+       Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
+       In case you need to break the iteration, *action* should return false.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       
+       node - (object) A <Graph.Node>.
+       levelBegin - (number) A relative level value.
+       levelEnd - (number) A relative level value.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+    */
+    eachLevel: function(node, levelBegin, levelEnd, action, flags) {
+        var d = node._depth, filter = this.filter(flags), that = this, shouldContinue = true;
+        levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
+        (function loopLevel(node, levelBegin, levelEnd) {
+            if(!shouldContinue) return;
+            var d = node._depth, ret;
+            if(d >= levelBegin && d <= levelEnd && filter(node)) ret = action(node, d);
+            if(typeof ret !== "undefined") shouldContinue = ret;
+            if(shouldContinue && d < levelEnd) {
+                that.eachAdjacency(node, function(adj) {
+                    var n = adj.nodeTo;
+                    if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
+                });
+            }
+        })(node, levelBegin + d, levelEnd + d);
+    },
+
+    /*
+       Method: eachSubgraph
+    
+       Iterates over a node's children recursively.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachSubgraph(node, function(node) {
+           alert(node.name);
+         });
+         //or...
+         node.eachSubgraph(function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachSubgraph: function(node, action, flags) {
+      this.eachLevel(node, 0, false, action, flags);
+    },
+
+    /*
+       Method: eachSubnode
+    
+       Iterates over a node's children (without deeper recursion).
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+       
+       Parameters:
+       node - (object) A <Graph.Node>.
+       action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.eachSubnode(node, function(node) {
+          alert(node.name);
+         });
+         //or...
+         node.eachSubnode(function(node) {
+           alert(node.name);
+         });
+       (end code)
+    */
+    eachSubnode: function(node, action, flags) {
+        this.eachLevel(node, 1, 1, action, flags);
+    },
+
+    /*
+       Method: anySubnode
+    
+       Returns *true* if any subnode matches the given condition.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+       cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
+
+       Example:
+       (start code js)
+         $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
+         //or...
+         node.anySubnode(function(node) { return node.name == 'mynodename'; });
+       (end code)
+    */
+    anySubnode: function(node, cond, flags) {
+      var flag = false;
+      cond = cond || $.lambda(true);
+      var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
+      this.eachSubnode(node, function(elem) {
+        if(c(elem)) flag = true;
+      }, flags);
+      return flag;
+    },
+  
+    /*
+       Method: getSubnodes
+    
+       Collects all subnodes for a specified node. 
+       The *level* parameter filters nodes having relative depth of *level* from the root node. 
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+       level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
+
+       Returns:
+       An array of nodes.
+
+    */
+    getSubnodes: function(node, level, flags) {
+        var ans = [], that = this;
+        level = level || 0;
+        var levelStart, levelEnd;
+        if($.type(level) == 'array') {
+            levelStart = level[0];
+            levelEnd = level[1];
+        } else {
+            levelStart = level;
+            levelEnd = Number.MAX_VALUE - node._depth;
+        }
+        this.eachLevel(node, levelStart, levelEnd, function(n) {
+            ans.push(n);
+        }, flags);
+        return ans;
+    },
+  
+  
+    /*
+       Method: getParents
+    
+       Returns an Array of <Graph.Nodes> which are parents of the given node.
+       
+       Also implemented by:
+       
+       <Graph.Node>.
+
+       Parameters:
+       node - (object) A <Graph.Node>.
+
+       Returns:
+       An Array of <Graph.Nodes>.
+
+       Example:
+       (start code js)
+         var pars = $jit.Graph.Util.getParents(node);
+         //or...
+         var pars = node.getParents();
+         
+         if(pars.length > 0) {
+           //do stuff with parents
+         }
+       (end code)
+    */
+    getParents: function(node) {
+        var ans = [];
+        this.eachAdjacency(node, function(adj) {
+            var n = adj.nodeTo;
+            if(n._depth < node._depth) ans.push(n);
+        });
+        return ans;
+    },
+    
+    /*
+    Method: isDescendantOf
+    Returns a boolean indicating if some node is descendant of the node with the given id. 
+
+    Also implemented by:
+    
+    <Graph.Node>.
+    
+    
+    Parameters:
+    node - (object) A <Graph.Node>.
+    id - (string) A <Graph.Node> id.
+
+    Example:
+    (start code js)
+      $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
+      //or...
+      node.isDescendantOf('nodeid');//true|false
+    (end code)
+ */
+ isDescendantOf: function(node, id) {
+    if(node.id == id) return true;
+    var pars = this.getParents(node), ans = false;
+    for ( var i = 0; !ans && i < pars.length; i++) {
+    ans = ans || this.isDescendantOf(pars[i], id);
+  }
+    return ans;
+ },
+
+ /*
+     Method: clean
+  
+     Cleans flags from nodes.
+
+     Also implemented by:
+     
+     <Graph>.
+     
+     Parameters:
+     graph - A <Graph> instance.
+  */
+  clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
+  
+  /* 
+    Method: getClosestNodeToOrigin 
+  
+    Returns the closest node to the center of canvas.
+  
+    Also implemented by:
+    
+    <Graph>.
+    
+    Parameters:
+   
+     graph - (object) A <Graph> instance.
+     prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
+  
+  */
+  getClosestNodeToOrigin: function(graph, prop, flags) {
+   return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
+  },
+  
+  /* 
+    Method: getClosestNodeToPos
+  
+    Returns the closest node to the given position.
+  
+    Also implemented by:
+    
+    <Graph>.
+    
+    Parameters:
+   
+     graph - (object) A <Graph> instance.
+     pos - (object) A <Complex> or <Polar> instance.
+     prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
+  
+  */
+  getClosestNodeToPos: function(graph, pos, prop, flags) {
+   var node = null;
+   prop = prop || 'current';
+   pos = pos && pos.getc(true) || Complex.KER;
+   var distance = function(a, b) {
+     var d1 = a.x - b.x, d2 = a.y - b.y;
+     return d1 * d1 + d2 * d2;
+   };
+   this.eachNode(graph, function(elem) {
+     node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
+         node.getPos(prop).getc(true), pos)) ? elem : node;
+   }, flags);
+   return node;
+  } 
+};
+
+//Append graph methods to <Graph>
+$.each(['get', 'getNode', 'each', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
+  Graph.prototype[m] = function() {
+    return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
+  };
+});
+
+//Append node methods to <Graph.Node>
+$.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
+  Graph.Node.prototype[m] = function() {
+    return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
+  };
+});
+
+
+/*
+ * File: Layouts.js
+ * 
+ * Implements base Tree and Graph layouts.
+ *
+ * Description:
+ *
+ * Implements base Tree and Graph layouts like Radial, Tree, etc.
+ * 
+ */
+
+/*
+ * Object: Layouts
+ * 
+ * Parent object for common layouts.
+ *
+ */
+var Layouts = $jit.Layouts = {};
+
+
+//Some util shared layout functions are defined here.
+var NodeDim = {
+  label: null,
+  
+  compute: function(graph, prop, opt) {
+    this.initializeLabel(opt);
+    var label = this.label, style = label.style;
+    graph.eachNode(function(n) {
+      var autoWidth  = n.getData('autoWidth'),
+          autoHeight = n.getData('autoHeight');
+      if(autoWidth || autoHeight) {
+        //delete dimensions since these are
+        //going to be overridden now.
+        delete n.data.$width;
+        delete n.data.$height;
+        delete n.data.$dim;
+        
+        var width  = n.getData('width'),
+            height = n.getData('height');
+        //reset label dimensions
+        style.width  = autoWidth? 'auto' : width + 'px';
+        style.height = autoHeight? 'auto' : height + 'px';
+        
+        //TODO(nico) should let the user choose what to insert here.
+        label.innerHTML = n.name;
+        
+        var offsetWidth  = label.offsetWidth,
+            offsetHeight = label.offsetHeight;
+        var type = n.getData('type');
+        if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
+          n.setData('width', offsetWidth);
+          n.setData('height', offsetHeight);
+        } else {
+          var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
+          n.setData('width', dim);
+          n.setData('height', dim);
+          n.setData('dim', dim); 
+        }
+      }
+    });
+  },
+  
+  initializeLabel: function(opt) {
+    if(!this.label) {
+      this.label = document.createElement('div');
+      document.body.appendChild(this.label);
+    }
+    this.setLabelStyles(opt);
+  },
+  
+  setLabelStyles: function(opt) {
+    $.extend(this.label.style, {
+      'visibility': 'hidden',
+      'position': 'absolute',
+      'width': 'auto',
+      'height': 'auto'
+    });
+    this.label.className = 'jit-autoadjust-label';
+  }
+};
+
+
+/*
+ * File: Layouts.MultiTopology.js
+ *
+*/
+
+/*
+ * Class: Layouts.MultiTopology
+ * 
+ * Implements a Multi Topology Layout.
+ * Adapted from Layouts.ForceDirected
+ * 
+ * Implemented By:
+ * 
+ * <MultiTopology>
+ * 
+ * Credits:
+ * 
+ * Marcus Cobden <http://marcuscobden.co.uk>
+ * 
+ */
+Layouts.MultiTopology = new Class({
+
+  getOptions: function(random) {
+    var s = this.canvas.getSize();
+    var w = s.width, h = s.height;
+    //count nodes
+    var count = 0;
+    this.graph.eachNode(function(n) { 
+      count++;
+    });
+    var k2 = w * h / count, k = Math.sqrt(k2);
+    var l = this.config.levelDistance;
+    
+    return {
+      width: w - l,
+      height: h - l,
+      tstart: w * 0.1,
+      nodef: function(x) { return k2 / (x || 1); },
+      edgef: function(x) { return /* x * x / k; */ k * (x - l); }
+    };
+  },
+  
+  compute: function(property, incremental) {
+    var prop = $.splat(property || ['current', 'start', 'end']);
+    var opt = this.getOptions();
+    NodeDim.compute(this.graph, prop, this.config);
+    this.graph.computeLevels(this.root, 0, "ignore");
+    this.graph.eachNode(function(n) {
+      $.each(prop, function(p) {
+        var pos = n.getPos(p);
+        if(pos.equals(Complex.KER)) {
+          pos.x = opt.width/5 * (Math.random() - 0.5);
+          pos.y = opt.height/5 * (Math.random() - 0.5);
+        }
+        //initialize disp vector
+        n.disp = {};
+        $.each(prop, function(p) {
+          n.disp[p] = $C(0, 0);
+        });
+      });
+    });
+    this.computePositions(prop, opt, incremental);
+  },
+  
+  computePositions: function(property, opt, incremental) {
+    var times = this.config.iterations, i = 0, that = this;
+    if(incremental) {
+      (function iter() {
+        for(var total=incremental.iter, j=0; j<total; j++) {
+          opt.t = opt.tstart;
+          if(times) opt.t *= (1 - i++/(times -1));
+          that.computePositionStep(property, opt);
+          if(times && i >= times) {
+            incremental.onComplete();
+            return;
+          }
+        }
+        incremental.onStep(Math.round(i / (times -1) * 100));
+        setTimeout(iter, 1);
+      })();
+    } else {
+      for(; i < times; i++) {
+        opt.t = opt.tstart * (1 - i/(times -1));
+        this.computePositionStep(property, opt);
+      }
+    }
+  },
+  
+  computePositionStep: function(property, opt) {
+    var graph = this.graph;
+    var min = Math.min, max = Math.max;
+    var dpos = $C(0, 0);
+    //calculate repulsive forces
+    graph.eachNode(function(v) {
+      //initialize disp
+      $.each(property, function(p) {
+        v.disp[p].x = 0; v.disp[p].y = 0;
+      });
+      graph.eachNode(function(u) {
+        if(u.id != v.id) {
+          $.each(property, function(p) {
+            var vp = v.getPos(p), up = u.getPos(p);
+            dpos.x = vp.x - up.x;
+            dpos.y = vp.y - up.y;
+            var norm = dpos.norm() || 1;
+            v.disp[p].$add(dpos
+                .$scale(opt.nodef(norm) / norm));
+          });
+        }
+      });
+    });
+    //calculate attractive forces
+    var T = !!graph.getNode(this.root).visited;
+    graph.eachNode(function(node) {
+      node.eachAdjacency(function(adj) {
+        var nodeTo = adj.nodeTo;
+        if(!!nodeTo.visited === T) {
+          $.each(property, function(p) {
+            var vp = node.getPos(p), up = nodeTo.getPos(p);
+            dpos.x = vp.x - up.x;
+            dpos.y = vp.y - up.y;
+            var norm = dpos.norm() || 1;
+            node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
+            nodeTo.disp[p].$add(dpos.$scale(-1));
+          });
+        }
+      });
+      node.visited = !T;
+    });
+    //arrange positions to fit the canvas
+    var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
+    graph.eachNode(function(u) {
+      $.each(property, function(p) {
+        var disp = u.disp[p];
+        var norm = disp.norm() || 1;
+        var p = u.getPos(p);
+        p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
+            disp.y * min(Math.abs(disp.y), t) / norm));
+        p.x = min(w2, max(-w2, p.x));
+        p.y = min(h2, max(-h2, p.y));
+      });
+    });
+  }
+});
+
+
+/*
+ * File: MultiTopology.js
+ */
+
+/*
+   Class: MultiTopology
+      
+   A network graph visualization adapted from the Force-Directed graph
+   
+  Implements:
+  
+  All <MultiLoader> methods
+  
+   Constructor Options:
+   
+   Inherits options from
+   
+   - <Options.Canvas>
+   - <Options.Controller>
+   - <Options.Node>
+   - <Options.Edge>
+   - <Options.Label>
+   - <Options.Events>
+   - <Options.Tips>
+   - <Options.NodeStyles>
+   - <Options.Navigation>
+   
+   Additionally, there are two parameters
+   
+   levelDistance - (number) Default's *50*. The natural length desired for the edges.
+   iterations - (number) Default's *50*. The number of iterations for the spring layout simulation. Depending on the browser's speed you could set this to a more 'interesting' number, like *200*. 
+     
+   Instance Properties:
+
+   canvas - Access a <Canvas> instance.
+   graph - Access a <Graph> instance.
+   op - Access a <ForceDirected.Op> instance.
+   fx - Access a <ForceDirected.Plot> instance.
+   labels - Access a <ForceDirected.Label> interface implementation.
+
+*/
+
+$jit.MultiTopology = new Class( {
+
+  Implements: [ MultiLoader, MultiExtras, Layouts.MultiTopology ],
+
+  initialize: function(controller) {
+    var $MultiTopology = $jit.MultiTopology;
+
+    var config = {
+      iterations: 50,
+      levelDistance: 50
+    };
+
+    this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
+        "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
+
+    var canvasConfig = this.config;
+    if(canvasConfig.useCanvas) {
+      this.canvas = canvasConfig.useCanvas;
+      this.config.labelContainer = this.canvas.id + '-label';
+    } else {
+      if(canvasConfig.background) {
+        canvasConfig.background = $.merge({
+          type: 'Circles'
+        }, canvasConfig.background);
+      }
+      this.canvas = new Canvas(this, canvasConfig);
+      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+    }
+
+    this.graphOptions = {
+      'klass': Complex,
+      'Node': {
+        'selected': false,
+        'exist': true,
+        'drawn': true
+      }
+    };
+    this.graph = new MultiGraph(this.graphOptions, this.config.Node,
+        this.config.Edge);
+    this.labels = new $MultiTopology.Label[canvasConfig.Label.type](this);
+    this.fx = new $MultiTopology.Plot(this, $MultiTopology);
+    this.op = new $MultiTopology.Op(this);
+    this.json = null;
+    this.busy = false;
+    // initialize extras
+    this.initializeExtras();
+  },
+
+  /* 
+    Method: refresh 
+    
+    Computes positions and plots the tree.
+  */
+  refresh: function() {
+    this.compute();
+    this.plot();
+  },
+
+  reposition: function() {
+    this.compute('end');
+  },
+
+/*
+  Method: computeIncremental
+  
+  Performs the Force Directed algorithm incrementally.
+  
+  Description:
+  
+  ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
+  This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
+  avoiding browser messages such as "This script is taking too long to complete".
+  
+  Parameters:
+  
+  opt - (object) The object properties are described below
+  
+  iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
+  of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
+  
+  property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
+  You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
+  computations for final animation positions then you can just choose 'end'.
+  
+  onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
+  parameter a percentage value.
+  
+  onComplete - A callback function called when the algorithm completed.
+  
+  Example:
+  
+  In this example I calculate the end positions and then animate the graph to those positions
+  
+  (start code js)
+  var fd = new $jit.ForceDirected(...);
+  fd.computeIncremental({
+    iter: 20,
+    property: 'end',
+    onStep: function(perc) {
+      Log.write("loading " + perc + "%");
+    },
+    onComplete: function() {
+      Log.write("done");
+      fd.animate();
+    }
+  });
+  (end code)
+  
+  In this example I calculate all positions and (re)plot the graph
+  
+  (start code js)
+  var fd = new ForceDirected(...);
+  fd.computeIncremental({
+    iter: 20,
+    property: ['end', 'start', 'current'],
+    onStep: function(perc) {
+      Log.write("loading " + perc + "%");
+    },
+    onComplete: function() {
+      Log.write("done");
+      fd.plot();
+    }
+  });
+  (end code)
+  
+  */
+  computeIncremental: function(opt) {
+    opt = $.merge( {
+      iter: 20,
+      property: 'end',
+      onStep: $.empty,
+      onComplete: $.empty
+    }, opt || {});
+
+    this.config.onBeforeCompute(this.graph.getNode(this.root));
+    this.compute(opt.property, opt);
+  },
+
+  /*
+    Method: plot
+   
+    Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
+   */
+  plot: function() {
+    this.fx.plot();
+  },
+
+  /*
+     Method: animate
+    
+     Animates the graph from the current positions to the 'end' node positions.
+  */
+  animate: function(opt) {
+    this.fx.animate($.merge( {
+      modes: [ 'linear' ]
+    }, opt || {}));
+  }
+});
+
+$jit.MultiTopology.$extend = true;
+
+(function(MultiTopology) {
+
+  /*
+     Class: ForceDirected.Op
+     
+     Custom extension of <Graph.Op>.
+
+     Extends:
+
+     All <Graph.Op> methods
+     
+     See also:
+     
+     <Graph.Op>
+
+  */
+  MultiTopology.Op = new Class( {
+
+    Implements: MultiGraph.Op
+
+  });
+
+  /*
+    Class: ForceDirected.Plot
+    
+    Custom extension of <Graph.Plot>.
+  
+    Extends:
+  
+    All <Graph.Plot> methods
+    
+    See also:
+    
+    <Graph.Plot>
+  
+  */
+  MultiTopology.Plot = new Class( {
+
+    Implements: MultiGraph.Plot
+
+  });
+
+  /*
+    Class: ForceDirected.Label
+    
+    Custom extension of <Graph.Label>. 
+    Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+  
+    Extends:
+  
+    All <Graph.Label> methods and subclasses.
+  
+    See also:
+  
+    <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+  
+  */
+  MultiTopology.Label = {};
+
+  /*
+     ForceDirected.Label.Native
+     
+     Custom extension of <Graph.Label.Native>.
+
+     Extends:
+
+     All <Graph.Label.Native> methods
+
+     See also:
+
+     <Graph.Label.Native>
+
+  */
+  MultiTopology.Label.Native = new Class( {
+    Implements: MultiGraph.Label.Native
+  });
+
+  /*
+    ForceDirected.Label.SVG
+    
+    Custom extension of <Graph.Label.SVG>.
+  
+    Extends:
+  
+    All <Graph.Label.SVG> methods
+  
+    See also:
+  
+    <Graph.Label.SVG>
+  
+  */
+  MultiTopology.Label.SVG = new Class( {
+    Implements: MultiGraph.Label.SVG,
+
+    initialize: function(viz) {
+      this.viz = viz;
+    },
+
+    /* 
+       placeLabel
+
+       Overrides abstract method placeLabel in <Graph.Label>.
+
+       Parameters:
+
+       tag - A DOM label element.
+       node - A <Graph.Node>.
+       controller - A configuration/controller object passed to the visualization.
+      
+     */
+    placeLabel: function(tag, node, controller) {
+      var pos = node.pos.getc(true), 
+          canvas = this.viz.canvas,
+          ox = canvas.translateOffsetX,
+          oy = canvas.translateOffsetY,
+          sx = canvas.scaleOffsetX,
+          sy = canvas.scaleOffsetY,
+          radius = canvas.getSize();
+      var labelPos = {
+        x: Math.round(pos.x * sx + ox + radius.width / 2),
+        y: Math.round(pos.y * sy + oy + radius.height / 2)
+      };
+      tag.setAttribute('x', labelPos.x);
+      tag.setAttribute('y', labelPos.y);
+
+      controller.onPlaceLabel(tag, node);
+    }
+  });
+
+  /*
+     ForceDirected.Label.HTML
+     
+     Custom extension of <Graph.Label.HTML>.
+
+     Extends:
+
+     All <Graph.Label.HTML> methods.
+
+     See also:
+
+     <Graph.Label.HTML>
+
+  */
+  MultiTopology.Label.HTML = new Class( {
+    Implements: MultiGraph.Label.HTML,
+
+    initialize: function(viz) {
+      this.viz = viz;
+    },
+    /* 
+       placeLabel
+
+       Overrides abstract method placeLabel in <Graph.Plot>.
+
+       Parameters:
+
+       tag - A DOM label element.
+       node - A <Graph.Node>.
+       controller - A configuration/controller object passed to the visualization.
+      
+     */
+    placeLabel: function(tag, node, controller) {
+      var pos = node.pos.getc(true), 
+          canvas = this.viz.canvas,
+          ox = canvas.translateOffsetX,
+          oy = canvas.translateOffsetY,
+          sx = canvas.scaleOffsetX,
+          sy = canvas.scaleOffsetY,
+          radius = canvas.getSize();
+      var labelPos = {
+        x: Math.round(pos.x * sx + ox + radius.width / 2),
+        y: Math.round(pos.y * sy + oy + radius.height / 2)
+      };
+      var style = tag.style;
+      style.left = labelPos.x + 'px';
+      style.top = labelPos.y + 'px';
+      style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
+
+      controller.onPlaceLabel(tag, node);
+    }
+  });
+
+  /*
+    Class: ForceDirected.Plot.NodeTypes
+
+    This class contains a list of <Graph.Node> built-in types. 
+    Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
+
+    You can add your custom node types, customizing your visualization to the extreme.
+
+    Example:
+
+    (start code js)
+      ForceDirected.Plot.NodeTypes.implement({
+        'mySpecialType': {
+          'render': function(node, canvas) {
+            //print your custom node to canvas
+          },
+          //optional
+          'contains': function(node, pos) {
+            //return true if pos is inside the node or false otherwise
+          }
+        }
+      });
+    (end code)
+
+  */
+  MultiTopology.Plot.NodeTypes = new Class({
+    'none': {
+      'render': $.empty,
+      'contains': $.lambda(false)
+    },
+       'host': {
+               'render': function(node, canvas, animating) {
+                       var pos = node.pos.getc(true);
+                       this.nodeHelper.host.render(pos, canvas, animating);
+               },
+               'contains': function(node, pos) {
+                       var npos = node.pos.getc(true);
+                       return this.nodeHelper.host.contains(npos, pos);
+               }
+       },
+       'swtch': {
+               'render': function(node, canvas, animating) {
+                       var pos = node.pos.getc(true);
+                       this.nodeHelper.swtch.render(pos, canvas, animating);
+               },
+               'contains': function(node, pos) {
+                       var npos = node.pos.getc(true);
+                       return this.nodeHelper.swtch.contains(npos, pos);
+               }
+       }
+  });
+
+  /*
+    Class: ForceDirected.Plot.EdgeTypes
+  
+    This class contains a list of <Graph.Adjacence> built-in types. 
+    Edge types implemented are 'none', 'line' and 'arrow'.
+  
+    You can add your custom edge types, customizing your visualization to the extreme.
+  
+    Example:
+  
+    (start code js)
+      ForceDirected.Plot.EdgeTypes.implement({
+        'mySpecialType': {
+          'render': function(adj, canvas) {
+            //print your custom edge to canvas
+          },
+          //optional
+          'contains': function(adj, pos) {
+            //return true if pos is inside the arc or false otherwise
+          }
+        }
+      });
+    (end code)
+  
+  */
+  MultiTopology.Plot.EdgeTypes = new Class({
+    'none': $.empty,
+    'line': {
+      'render': function(adj, alpha, canvas) {
+        var from = adj.nodeFrom.pos.getc(true),
+            to = adj.nodeTo.pos.getc(true);
+        this.edgeHelper.line.render(from, to, alpha, canvas);
+      },
+      'contains': function(adj, alpha, pos, canvas) {
+        var from = adj.nodeFrom.pos.getc(true),
+            to = adj.nodeTo.pos.getc(true);
+        return this.edgeHelper.line.contains(from, to, alpha, pos, this.edge.epsilon, canvas);
+      }
+    }
+  });
+
+})($jit.MultiTopology);
+
+
+
+
+ })();
diff --git a/opendaylight/web/root/src/main/resources/js/jquery-1.9.1.min.js b/opendaylight/web/root/src/main/resources/js/jquery-1.9.1.min.js
new file mode 100644 (file)
index 0000000..006e953
--- /dev/null
@@ -0,0 +1,5 @@
+/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery.min.map
+*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;
+return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="<a name='"+x+"'></a><div name='"+x+"'></div>",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&&gt(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Nt=/^(?:checkbox|radio)$/i,Ct=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l)
+}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=ln(e,t),Pt.detach()),Gt[e]=n),n}function ln(e,t){var n=b(t.createElement(e)).appendTo(t.body),r=b.css(n[0],"display");return n.remove(),r}b.each(["height","width"],function(e,n){b.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(b.css(e,"display"))?b.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,i),i):0)}}}),b.support.opacity||(b.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=b.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===b.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),b(function(){b.support.reliableMarginRight||(b.cssHooks.marginRight={get:function(e,n){return n?b.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!b.support.pixelPosition&&b.fn.position&&b.each(["top","left"],function(e,n){b.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?b(e).position()[n]+"px":r):t}}})}),b.expr&&b.expr.filters&&(b.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!b.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||b.css(e,"display"))},b.expr.filters.visible=function(e){return!b.expr.filters.hidden(e)}),b.each({margin:"",padding:"",border:"Width"},function(e,t){b.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(b.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;b.fn.extend({serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=b.prop(this,"elements");return e?b.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!b(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Nt.test(e))}).map(function(e,t){var n=b(this).val();return null==n?null:b.isArray(n)?b.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),b.param=function(e,n){var r,i=[],o=function(e,t){t=b.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=b.ajaxSettings&&b.ajaxSettings.traditional),b.isArray(e)||e.jquery&&!b.isPlainObject(e))b.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(b.isArray(t))b.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==b.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}b.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){b.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),b.fn.hover=function(e,t){return this.mouseenter(e).mouseleave(t||e)};var mn,yn,vn=b.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Nn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Cn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=b.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=a.href}catch(Ln){yn=o.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(b.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(u){var l;return o[u]=!0,b.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||a||o[c]?a?!(l=c):t:(n.dataTypes.unshift(c),s(c),!1)}),l}return s(n.dataTypes[0])||!o["*"]&&s("*")}function Mn(e,n){var r,i,o=b.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&b.extend(!0,e,r),e}b.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),b.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&b.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?b("<div>").append(b.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},b.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){b.fn[t]=function(e){return this.on(t,e)}}),b.each(["get","post"],function(e,n){b[n]=function(e,r,i,o){return b.isFunction(r)&&(o=o||i,i=r,r=t),b.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),b.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Nn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Mn(Mn(e,b.ajaxSettings),t):Mn(b.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,u,l,c,p=b.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?b(f):b.event,h=b.Deferred(),g=b.Callbacks("once memory"),m=p.statusCode||{},y={},v={},x=0,T="canceled",N={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)m[t]=[m[t],e[t]];else N.always(e[N.status]);return this},abort:function(e){var t=e||T;return l&&l.abort(t),k(0,t),this}};if(h.promise(N).complete=g.add,N.success=N.done,N.error=N.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=b.trim(p.dataType||"*").toLowerCase().match(w)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?80:443))==(mn[3]||("http:"===mn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=b.param(p.data,p.traditional)),qn(An,p,n,N),2===x)return N;u=p.global,u&&0===b.active++&&b.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Cn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(b.lastModified[o]&&N.setRequestHeader("If-Modified-Since",b.lastModified[o]),b.etag[o]&&N.setRequestHeader("If-None-Match",b.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&N.setRequestHeader("Content-Type",p.contentType),N.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)N.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,N,p)===!1||2===x))return N.abort();T="abort";for(i in{success:1,error:1,complete:1})N[i](p[i]);if(l=qn(jn,p,n,N)){N.readyState=1,u&&d.trigger("ajaxSend",[N,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){N.abort("timeout")},p.timeout));try{x=1,l.send(y,k)}catch(C){if(!(2>x))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,C=n;2!==x&&(x=2,s&&clearTimeout(s),l=t,a=i||"",N.readyState=e>0?4:0,r&&(w=_n(p,N,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=N.getResponseHeader("Last-Modified"),T&&(b.lastModified[o]=T),T=N.getResponseHeader("etag"),T&&(b.etag[o]=T)),204===e?(c=!0,C="nocontent"):304===e?(c=!0,C="notmodified"):(c=Fn(p,w),C=c.state,y=c.data,v=c.error,c=!v)):(v=C,(e||!C)&&(C="error",0>e&&(e=0))),N.status=e,N.statusText=(n||C)+"",c?h.resolveWith(f,[y,C,N]):h.rejectWith(f,[N,C,v]),N.statusCode(m),m=t,u&&d.trigger(c?"ajaxSuccess":"ajaxError",[N,p,c?y:v]),g.fireWith(f,[N,C]),u&&(d.trigger("ajaxComplete",[N,p]),--b.active||b.event.trigger("ajaxStop")))}return N},getScript:function(e,n){return b.get(e,t,n,"script")},getJSON:function(e,t,n){return b.get(e,t,n,"json")}});function _n(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(s in c)s in r&&(n[c[s]]=r[s]);while("*"===l[0])l.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in u)if(u[s]&&u[s].test(o)){l.unshift(s);break}if(l[0]in r)a=l[0];else{for(s in r){if(!l[0]||e.converters[s+" "+l[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function Fn(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(i in e.converters)a[i.toLowerCase()]=e.converters[i];for(;r=u[++s];)if("*"!==r){if("*"!==l&&l!==r){if(i=a[l+" "+r]||a["* "+r],!i)for(n in a)if(o=n.split(" "),o[1]===r&&(i=a[l+" "+o[0]]||a["* "+o[0]])){i===!0?i=a[n]:a[n]!==!0&&(r=o[0],u.splice(s--,0,r));break}if(i!==!0)if(i&&e["throws"])t=i(t);else try{t=i(t)}catch(c){return{state:"parsererror",error:i?c:"No conversion from "+l+" to "+r}}}l=r}return{state:"success",data:t}}b.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return b.globalEval(e),e}}}),b.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),b.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=o.head||b("head")[0]||o.documentElement;return{send:function(t,i){n=o.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var On=[],Bn=/(=)\?(?=&|$)|\?\?/;b.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=On.pop()||b.expando+"_"+vn++;return this[e]=!0,e}}),b.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=b.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||b.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,On.push(o)),s&&b.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}b.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=b.ajaxSettings.xhr(),b.support.cors=!!Rn&&"withCredentials"in Rn,Rn=b.support.ajax=!!Rn,Rn&&b.ajaxTransport(function(n){if(!n.crossDomain||b.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=b.noop,$n&&delete Pn[a]),i)4!==u.readyState&&u.abort();else{p={},s=u.status,l=u.getAllResponseHeaders(),"string"==typeof u.responseText&&(p.text=u.responseText);try{c=u.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,l)},n.async?4===u.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},b(e).unload($n)),Pn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+x+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=Yn.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(b.cssNumber[e]?"":"px"),"px"!==r&&s){s=b.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,b.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=b.now()}function Zn(e,t){b.each(t,function(t,n){var r=(Qn[t]||[]).concat(Qn["*"]),i=0,o=r.length;for(;o>i;i++)if(r[i].call(e,t,n))return})}function er(e,t,n){var r,i,o=0,a=Gn.length,s=b.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;for(;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:b.extend({},t),opts:b.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=b.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(tr(c,l.opts.specialEasing);a>o;o++)if(r=Gn[o].call(l,e,c,l.opts))return r;return Zn(l,c),b.isFunction(l.opts.start)&&l.opts.start.call(e,l),b.fx.timer(b.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function tr(e,t){var n,r,i,o,a;for(i in e)if(r=b.camelCase(i),o=t[r],n=e[i],b.isArray(n)&&(o=n[1],n=e[i]=n[0]),i!==r&&(e[r]=n,delete e[i]),a=b.cssHooks[r],a&&"expand"in a){n=a.expand(n),delete e[r];for(i in n)i in e||(e[i]=n[i],t[i]=o)}else t[r]=o}b.Animation=b.extend(er,{tweener:function(e,t){b.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,u,l,c,p,f=this,d=e.style,h={},g=[],m=e.nodeType&&nn(e);n.queue||(c=b._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,p=c.empty.fire,c.empty.fire=function(){c.unqueued||p()}),c.unqueued++,f.always(function(){f.always(function(){c.unqueued--,b.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===b.css(e,"display")&&"none"===b.css(e,"float")&&(b.support.inlineBlockNeedsLayout&&"inline"!==un(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",b.support.shrinkWrapBlocks||f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(i in t)if(a=t[i],Vn.exec(a)){if(delete t[i],u=u||"toggle"===a,a===(m?"hide":"show"))continue;g.push(i)}if(o=g.length){s=b._data(e,"fxshow")||b._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?b(e).show():f.done(function(){b(e).hide()}),f.done(function(){var t;b._removeData(e,"fxshow");for(t in h)b.style(e,t,h[t])});for(i=0;o>i;i++)r=g[i],l=f.createTween(r,m?s[r]:0),h[r]=s[r]||b.style(e,r),r in s||(s[r]=l.start,m&&(l.end=l.start,l.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}b.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(b.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?b.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=b.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){b.fx.step[e.prop]?b.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[b.cssProps[e.prop]]||b.cssHooks[e.prop])?b.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},b.each(["toggle","show","hide"],function(e,t){var n=b.fn[t];b.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),b.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=b.isEmptyObject(e),o=b.speed(t,n,r),a=function(){var t=er(this,b.extend({},e),o);a.finish=function(){t.stop(!0)},(i||b._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=b.timers,a=b._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&b.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=b._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=b.timers,a=r?r.length:0;for(n.finish=!0,b.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}b.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){b.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),b.speed=function(e,t,n){var r=e&&"object"==typeof e?b.extend({},e):{complete:n||!n&&t||b.isFunction(e)&&e,duration:e,easing:n&&t||t&&!b.isFunction(t)&&t};return r.duration=b.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in b.fx.speeds?b.fx.speeds[r.duration]:b.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){b.isFunction(r.old)&&r.old.call(this),r.queue&&b.dequeue(this,r.queue)},r},b.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},b.timers=[],b.fx=rr.prototype.init,b.fx.tick=function(){var e,n=b.timers,r=0;for(Xn=b.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||b.fx.stop(),Xn=t},b.fx.timer=function(e){e()&&b.timers.push(e)&&b.fx.start()},b.fx.interval=13,b.fx.start=function(){Un||(Un=setInterval(b.fx.tick,b.fx.interval))},b.fx.stop=function(){clearInterval(Un),Un=null},b.fx.speeds={slow:600,fast:200,_default:400},b.fx.step={},b.expr&&b.expr.filters&&(b.expr.filters.animated=function(e){return b.grep(b.timers,function(t){return e===t.elem}).length}),b.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){b.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,b.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},b.offset={setOffset:function(e,t,n){var r=b.css(e,"position");"static"===r&&(e.style.position="relative");var i=b(e),o=i.offset(),a=b.css(e,"top"),s=b.css(e,"left"),u=("absolute"===r||"fixed"===r)&&b.inArray("auto",[a,s])>-1,l={},c={},p,f;u?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),b.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(l.top=t.top-o.top+p),null!=t.left&&(l.left=t.left-o.left+f),"using"in t?t.using.call(e,l):i.css(l)}},b.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===b.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),b.nodeName(e[0],"html")||(n=e.offset()),n.top+=b.css(e[0],"borderTopWidth",!0),n.left+=b.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-b.css(r,"marginTop",!0),left:t.left-n.left-b.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||o.documentElement;while(e&&!b.nodeName(e,"html")&&"static"===b.css(e,"position"))e=e.offsetParent;return e||o.documentElement})}}),b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);b.fn[e]=function(i){return b.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?b(a).scrollLeft():o,r?o:b(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return b.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}b.each({Height:"height",Width:"width"},function(e,n){b.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){b.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return b.access(this,function(n,r,i){var o;return b.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?b.css(n,r,s):b.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=b,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return b})})(window);
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/js/less-1.3.3.min.js b/opendaylight/web/root/src/main/resources/js/less-1.3.3.min.js
new file mode 100644 (file)
index 0000000..9b0fa6b
--- /dev/null
@@ -0,0 +1,9 @@
+//
+// LESS - Leaner CSS v1.3.3
+// http://lesscss.org
+// 
+// Copyright (c) 2009-2013, Alexis Sellier
+// Licensed under the Apache 2.0 License.
+//
+(function(e,t){function n(t){return e.less[t.split("/")[1]]}function f(){r.env==="development"?(r.optimization=0,r.watchTimer=setInterval(function(){r.watchMode&&g(function(e,t,n,r,i){t&&S(t.toCSS(),r,i.lastModified)})},r.poll)):r.optimization=3}function m(){var e=document.getElementsByTagName("style");for(var t=0;t<e.length;t++)e[t].type.match(p)&&(new r.Parser({filename:document.location.href.replace(/#.*$/,""),dumpLineNumbers:r.dumpLineNumbers})).parse(e[t].innerHTML||"",function(n,r){var i=r.toCSS(),s=e[t];s.type="text/css",s.styleSheet?s.styleSheet.cssText=i:s.innerHTML=i})}function g(e,t){for(var n=0;n<r.sheets.length;n++)w(r.sheets[n],e,t,r.sheets.length-(n+1))}function y(e,t){var n=b(e),r=b(t),i,s,o,u,a="";if(n.hostPart!==r.hostPart)return"";s=Math.max(r.directories.length,n.directories.length);for(i=0;i<s;i++)if(r.directories[i]!==n.directories[i])break;u=r.directories.slice(i),o=n.directories.slice(i);for(i=0;i<u.length-1;i++)a+="../";for(i=0;i<o.length-1;i++)a+=o[i]+"/";return a}function b(e,t){var n=/^((?:[a-z-]+:)?\/\/(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/,r=e.match(n),i={},s=[],o,u;if(!r)throw new Error("Could not parse sheet href - '"+e+"'");if(!r[1]||r[2]){u=t.match(n);if(!u)throw new Error("Could not parse page url - '"+t+"'");r[1]=u[1],r[2]||(r[3]=u[3]+r[3])}if(r[3]){s=r[3].replace("\\","/").split("/");for(o=0;o<s.length;o++)s[o]===".."&&o>0&&(s.splice(o-1,2),o-=2)}return i.hostPart=r[1],i.directories=s,i.path=r[1]+s.join("/"),i.fileUrl=i.path+(r[4]||""),i.url=i.fileUrl+(r[5]||""),i}function w(t,n,i,s){var o=t.contents||{},u=t.files||{},a=b(t.href,e.location.href),f=a.url,c=l&&l.getItem(f),h=l&&l.getItem(f+":timestamp"),p={css:c,timestamp:h},d;r.relativeUrls?r.rootpath?t.entryPath?d=b(r.rootpath+y(a.path,t.entryPath)).path:d=r.rootpath:d=a.path:r.rootpath?d=r.rootpath:t.entryPath?d=t.entryPath:d=a.path,x(f,t.type,function(e,l){v+=e.replace(/@import .+?;/ig,"");if(!i&&p&&l&&(new Date(l)).valueOf()===(new Date(p.timestamp)).valueOf())S(p.css,t),n(null,null,e,t,{local:!0,remaining:s},f);else try{o[f]=e,(new r.Parser({optimization:r.optimization,paths:[a.path],entryPath:t.entryPath||a.path,mime:t.type,filename:f,rootpath:d,relativeUrls:t.relativeUrls,contents:o,files:u,dumpLineNumbers:r.dumpLineNumbers})).parse(e,function(r,i){if(r)return k(r,f);try{n(r,i,e,t,{local:!1,lastModified:l,remaining:s},f),N(document.getElementById("less-error-message:"+E(f)))}catch(r){k(r,f)}})}catch(c){k(c,f)}},function(e,t){throw new Error("Couldn't load "+t+" ("+e+")")})}function E(e){return e.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function S(e,t,n){var r,i=t.href||"",s="less:"+(t.title||E(i));if((r=document.getElementById(s))===null){r=document.createElement("style"),r.type="text/css",t.media&&(r.media=t.media),r.id=s;var o=t&&t.nextSibling||null;(o||document.getElementsByTagName("head")[0]).parentNode.insertBefore(r,o)}if(r.styleSheet)try{r.styleSheet.cssText=e}catch(u){throw new Error("Couldn't reassign styleSheet.cssText.")}else(function(e){r.childNodes.length>0?r.firstChild.nodeValue!==e.nodeValue&&r.replaceChild(e,r.firstChild):r.appendChild(e)})(document.createTextNode(e));if(n&&l){C("saving "+i+" to cache.");try{l.setItem(i,e),l.setItem(i+":timestamp",n)}catch(u){C("failed to save")}}}function x(e,t,n,i){function a(t,n,r){t.status>=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):typeof r=="function"&&r(t.status,e)}var s=T(),u=o?r.fileAsync:r.async;typeof s.overrideMimeType=="function"&&s.overrideMimeType("text/css"),s.open("GET",e,u),s.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),s.send(null),o&&!r.fileAsync?s.status===0||s.status>=200&&s.status<300?n(s.responseText):i(s.status,e):u?s.onreadystatechange=function(){s.readyState==4&&a(s,n,i)}:a(s,n,i)}function T(){if(e.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(t){return C("browser doesn't support AJAX."),null}}function N(e){return e&&e.parentNode.removeChild(e)}function C(e){r.env=="development"&&typeof console!="undefined"&&console.log("less: "+e)}function k(e,t){var n="less-error-message:"+E(t),i='<li><label>{line}</label><pre class="{class}">{content}</pre></li>',s=document.createElement("div"),o,u,a=[],f=e.filename||t,l=f.match(/([^\/]+(\?.*)?)$/)[1];s.id=n,s.className="less-error-message",u="<h3>"+(e.message||"There is an error in your .less file")+"</h3>"+'<p>in <a href="'+f+'">'+l+"</a> ";var c=function(e,t,n){e.extract[t]&&a.push(i.replace(/\{line\}/,parseInt(e.line)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};e.stack?u+="<br/>"+e.stack.split("\n").slice(1).join("<br/>"):e.extract&&(c(e,0,""),c(e,1,"line"),c(e,2,""),u+="on line "+e.line+", column "+(e.column+1)+":</p>"+"<ul>"+a.join("")+"</ul>"),s.innerHTML=u,S([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),s.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),r.env=="development"&&(o=setInterval(function(){document.body&&(document.getElementById(n)?document.body.replaceChild(s,document.getElementById(n)):document.body.insertBefore(s,document.body.firstChild),clearInterval(o))},10))}Array.isArray||(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"||e instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){var n=this.length>>>0;for(var r=0;r<n;r++)r in this&&e.call(t,this[r],r,this)}),Array.prototype.map||(Array.prototype.map=function(e){var t=this.length>>>0,n=new Array(t),r=arguments[1];for(var i=0;i<t;i++)i in this&&(n[i]=e.call(r,this[i],i,this));return n}),Array.prototype.filter||(Array.prototype.filter=function(e){var t=[],n=arguments[1];for(var r=0;r<this.length;r++)e.call(n,this[r])&&t.push(this[r]);return t}),Array.prototype.reduce||(Array.prototype.reduce=function(e){var t=this.length>>>0,n=0;if(t===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n<t;n++)n in this&&(r=e.call(null,r,this[n],n,this));return r}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e){var t=this.length,n=arguments[1]||0;if(!t)return-1;if(n>=t)return-1;n<0&&(n+=t);for(;n<t;n++){if(!Object.prototype.hasOwnProperty.call(this,n))continue;if(e===this[n])return n}return-1}),Object.keys||(Object.keys=function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}),String.prototype.trim||(String.prototype.trim=function(){return String(this).replace(/^\s\s*/,"").replace(/\s\s*$/,"")});var r,i,s;typeof environment=="object"&&{}.toString.call(environment)==="[object Environment]"?(typeof e=="undefined"?r={}:r=e.less={},i=r.tree={},r.mode="rhino"):typeof e=="undefined"?(r=exports,i=n("./tree"),r.mode="node"):(typeof e.less=="undefined"&&(e.less={}),r=e.less,i=e.less.tree={},r.mode="browser"),r.Parser=function(t){function g(){a=c[u],f=o,h=o}function y(){c[u]=a,o=f,h=o}function b(){o>h&&(c[u]=c[u].slice(o-h),h=o)}function w(e){var t=e.charCodeAt(0);return t===32||t===10||t===9}function E(e){var t,n,r,i,a;if(e instanceof Function)return e.call(p.parsers);if(typeof e=="string")t=s.charAt(o)===e?e:null,r=1,b();else{b();if(!(t=e.exec(c[u])))return null;r=t[0].length}if(t)return S(r),typeof t=="string"?t:t.length===1?t[0]:t}function S(e){var t=o,n=u,r=o+c[u].length,i=o+=e;while(o<r){if(!w(s.charAt(o)))break;o++}return c[u]=c[u].slice(e+(o-i)),h=o,c[u].length===0&&u<c.length-1&&u++,t!==o||n!==u}function x(e,t){var n=E(e);if(!!n)return n;T(t||(typeof e=="string"?"expected '"+e+"' got '"+s.charAt(o)+"'":"unexpected token"))}function T(e,t){var n=new Error(e);throw n.index=o,n.type=t||"Syntax",n}function N(e){return typeof e=="string"?s.charAt(o)===e:e.test(c[u])?!0:!1}function C(e,t){return e.filename&&t.filename&&e.filename!==t.filename?p.imports.contents[e.filename]:s}function k(e,t){for(var n=e,r=-1;n>=0&&t.charAt(n)!=="\n";n--)r++;return{line:typeof e=="number"?(t.slice(0,e).match(/\n/g)||"").length:null,column:r}}function L(e){return r.mode==="browser"||r.mode==="rhino"?e.filename:n("path").resolve(e.filename)}function A(e,t,n){return{lineNumber:k(e,t).line+1,fileName:L(n)}}function O(e,t){var n=C(e,t),r=k(e.index,n),i=r.line,s=r.column,o=n.split("\n");this.type=e.type||"Syntax",this.message=e.message,this.filename=e.filename||t.filename,this.index=e.index,this.line=typeof i=="number"?i+1:null,this.callLine=e.call&&k(e.call,n).line+1,this.callExtract=o[k(e.call,n).line],this.stack=e.stack,this.column=s,this.extract=[o[i-1],o[i],o[i+1]]}var s,o,u,a,f,l,c,h,p,d=this,t=t||{};t.contents||(t.contents={}),t.rootpath=t.rootpath||"",t.files||(t.files={});var v=function(){},m=this.imports={paths:t.paths||[],queue:[],files:t.files,contents:t.contents,mime:t.mime,error:null,push:function(e,n){var i=this;this.queue.push(e),r.Parser.importer(e,this.paths,function(t,r,s){i.queue.splice(i.queue.indexOf(e),1);var o=s in i.files;i.files[s]=r,t&&!i.error&&(i.error=t),n(t,r,o),i.queue.length===0&&v(i.error)},t)}};return this.env=t=t||{},this.optimization="optimization"in this.env?this.env.optimization:1,this.env.filename=this.env.filename||null,p={imports:m,parse:function(e,a){var f,d,m,g,y,b,w=[],S,x=null;o=u=h=l=0,s=e.replace(/\r\n/g,"\n"),s=s.replace(/^\uFEFF/,""),c=function(e){var n=0,r=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,i=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,o=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,u=0,a,f=e[0],l;for(var c=0,h,p;c<s.length;){r.lastIndex=c,(a=r.exec(s))&&a.index===c&&(c+=a[0].length,f.push(a[0])),h=s.charAt(c),i.lastIndex=o.lastIndex=c;if(a=o.exec(s))if(a.index===c){c+=a[0].length,f.push(a[0]);continue}if(!l&&h==="/"){p=s.charAt(c+1);if(p==="/"||p==="*")if(a=i.exec(s))if(a.index===c){c+=a[0].length,f.push(a[0]);continue}}switch(h){case"{":if(!l){u++,f.push(h);break};case"}":if(!l){u--,f.push(h),e[++n]=f=[];break};case"(":if(!l){l=!0,f.push(h);break};case")":if(l){l=!1,f.push(h);break};default:f.push(h)}c++}return u!=0&&(x=new O({index:c-1,type:"Parse",message:u>0?"missing closing `}`":"missing opening `{`",filename:t.filename},t)),e.map(function(e){return e.join("")})}([[]]);if(x)return a(x,t);try{f=new i.Ruleset([],E(this.parsers.primary)),f.root=!0}catch(T){return a(new O(T,t))}f.toCSS=function(e){var s,o,u;return function(s,o){var u=[],a;s=s||{},typeof o=="object"&&!Array.isArray(o)&&(o=Object.keys(o).map(function(e){var t=o[e];return t instanceof i.Value||(t instanceof i.Expression||(t=new i.Expression([t])),t=new i.Value([t])),new i.Rule("@"+e,t,!1,0)}),u=[new i.Ruleset(null,o)]);try{var f=e.call(this,{frames:u}).toCSS([],{compress:s.compress||!1,dumpLineNumbers:t.dumpLineNumbers})}catch(l){throw new O(l,t)}if(a=p.imports.error)throw a instanceof O?a:new O(a,t);return s.yuicompress&&r.mode==="node"?n("ycssmin").cssmin(f):s.compress?f.replace(/(\s)+/g,"$1"):f}}(f.eval);if(o<s.length-1){o=l,b=s.split("\n"),y=(s.slice(0,o).match(/\n/g)||"").length+1;for(var N=o,C=-1;N>=0&&s.charAt(N)!=="\n";N--)C++;x={type:"Parse",message:"Syntax Error on line "+y,index:o,filename:t.filename,line:y,column:C,extract:[b[y-2],b[y-1],b[y]]}}this.imports.queue.length>0?v=function(e){e=x||e,e?a(e):a(null,f)}:a(x,f)},parsers:{primary:function(){var e,t=[];while((e=E(this.mixin.definition)||E(this.rule)||E(this.ruleset)||E(this.mixin.call)||E(this.comment)||E(this.directive))||E(/^[\s\n]+/)||E(/^;+/))e&&t.push(e);return t},comment:function(){var e;if(s.charAt(o)!=="/")return;if(s.charAt(o+1)==="/")return new i.Comment(E(/^\/\/.*/),!0);if(e=E(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new i.Comment(e)},entities:{quoted:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=='"'&&s.charAt(t)!=="'")return;n&&E("~");if(e=E(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new i.Quoted(e[0],e[1]||e[2],n)},keyword:function(){var e;if(e=E(/^[_A-Za-z-][_A-Za-z0-9-]*/))return i.colors.hasOwnProperty(e)?new i.Color(i.colors[e].slice(1)):new i.Keyword(e)},call:function(){var e,n,r,s,a=o;if(!(e=/^([\w-]+|%|progid:[\w\.]+)\(/.exec(c[u])))return;e=e[1],n=e.toLowerCase();if(n==="url")return null;o+=e.length;if(n==="alpha"){s=E(this.alpha);if(typeof s!="undefined")return s}E("("),r=E(this.entities.arguments);if(!E(")"))return;if(e)return new i.Call(e,r,a,t.filename)},arguments:function(){var e=[],t;while(t=E(this.entities.assignment)||E(this.expression)){e.push(t);if(!E(","))break}return e},literal:function(){return E(this.entities.ratio)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.quoted)||E(this.entities.unicodeDescriptor)},assignment:function(){var e,t;if((e=E(/^\w+(?=\s?=)/i))&&E("=")&&(t=E(this.entity)))return new i.Assignment(e,t)},url:function(){var e;if(s.charAt(o)!=="u"||!E(/^url\(/))return;return e=E(this.entities.quoted)||E(this.entities.variable)||E(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/)||"",x(")"),new i.URL(e.value!=null||e instanceof i.Variable?e:new i.Anonymous(e),t.rootpath)},variable:function(){var e,n=o;if(s.charAt(o)==="@"&&(e=E(/^@@?[\w-]+/)))return new i.Variable(e,n,t.filename)},variableCurly:function(){var e,n,r=o;if(s.charAt(o)==="@"&&(n=E(/^@\{([\w-]+)\}/)))return new i.Variable("@"+n[1],r,t.filename)},color:function(){var e;if(s.charAt(o)==="#"&&(e=E(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/)))return new i.Color(e[1])},dimension:function(){var e,t=s.charCodeAt(o);if(t>57||t<43||t===47||t==44)return;if(e=E(/^([+-]?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn|dpi|dpcm|dppx|rem|vw|vh|vmin|vm|ch)?/))return new i.Dimension(e[1],e[2])},ratio:function(){var e,t=s.charCodeAt(o);if(t>57||t<48)return;if(e=E(/^(\d+\/\d+)/))return new i.Ratio(e[1])},unicodeDescriptor:function(){var e;if(e=E(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/))return new i.UnicodeDescriptor(e[0])},javascript:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=="`")return;n&&E("~");if(e=E(/^`([^`]*)`/))return new i.JavaScript(e[1],o,n)}},variable:function(){var e;if(s.charAt(o)==="@"&&(e=E(/^(@[\w-]+)\s*:/)))return e[1]},shorthand:function(){var e,t;if(!N(/^[@\w.%-]+\/[@\w.-]+/))return;g();if((e=E(this.entity))&&E("/")&&(t=E(this.entity)))return new i.Shorthand(e,t);y()},mixin:{call:function(){var e=[],n,r,u=[],a=[],f,l,c,h,p,d,v,m=o,b=s.charAt(o),w,S,C=!1;if(b!=="."&&b!=="#")return;g();while(n=E(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/))e.push(new i.Element(r,n,o)),r=E(">");if(E("(")){p=[];while(c=E(this.expression)){h=null,S=c;if(c.value.length==1){var k=c.value[0];k instanceof i.Variable&&E(":")&&(p.length>0&&(d&&T("Cannot mix ; and , as delimiter types"),v=!0),S=x(this.expression),h=w=k.name)}p.push(S),a.push({name:h,value:S});if(E(","))continue;if(E(";")||d)v&&T("Cannot mix ; and , as delimiter types"),d=!0,p.length>1&&(S=new i.Value(p)),u.push({name:w,value:S}),w=null,p=[],v=!1}x(")")}f=d?u:a,E(this.important)&&(C=!0);if(e.length>0&&(E(";")||N("}")))return new i.mixin.Call(e,f,m,t.filename,C);y()},definition:function(){var e,t=[],n,r,u,a,f,c=!1;if(s.charAt(o)!=="."&&s.charAt(o)!=="#"||N(/^[^{]*\}/))return;g();if(n=E(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=n[1];do{E(this.comment);if(s.charAt(o)==="."&&E(/^\.{3}/)){c=!0,t.push({variadic:!0});break}if(!(u=E(this.entities.variable)||E(this.entities.literal)||E(this.entities.keyword)))break;if(u instanceof i.Variable)if(E(":"))a=x(this.expression,"expected expression"),t.push({name:u.name,value:a});else{if(E(/^\.{3}/)){t.push({name:u.name,variadic:!0}),c=!0;break}t.push({name:u.name})}else t.push({value:u})}while(E(",")||E(";"));E(")")||(l=o,y()),E(this.comment),E(/^when/)&&(f=x(this.conditions,"expected condition")),r=E(this.block);if(r)return new i.mixin.Definition(e,t,r,f,c);y()}}},entity:function(){return E(this.entities.literal)||E(this.entities.variable)||E(this.entities.url)||E(this.entities.call)||E(this.entities.keyword)||E(this.entities.javascript)||E(this.comment)},end:function(){return E(";")||N("}")},alpha:function(){var e;if(!E(/^\(opacity=/i))return;if(e=E(/^\d+/)||E(this.entities.variable))return x(")"),new i.Alpha(e)},element:function(){var e,t,n,r;n=E(this.combinator),e=E(/^(?:\d+\.\d+|\d+)%/)||E(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||E("*")||E("&")||E(this.attribute)||E(/^\([^()@]+\)/)||E(/^[\.#](?=@)/)||E(this.entities.variableCurly),e||E("(")&&(r=E(this.entities.variableCurly)||E(this.entities.variable)||E(this.selector))&&E(")")&&(e=new i.Paren(r));if(e)return new i.Element(n,e,o)},combinator:function(){var e,t=s.charAt(o);if(t===">"||t==="+"||t==="~"||t==="|"){o++;while(s.charAt(o).match(/\s/))o++;return new i.Combinator(t)}return s.charAt(o-1).match(/\s/)?new i.Combinator(" "):new i.Combinator(null)},selector:function(){var e,t,n=[],r,u;if(E("("))return e=E(this.entity),E(")")?new i.Selector([new i.Element("",e,o)]):null;while(t=E(this.element)){r=s.charAt(o),n.push(t);if(r==="{"||r==="}"||r===";"||r===","||r===")")break}if(n.length>0)return new i.Selector(n)},attribute:function(){var e="",t,n,r;if(!E("["))return;if(t=E(/^(?:[_A-Za-z0-9-]|\\.)+/)||E(this.entities.quoted))(r=E(/^[|~*$^]?=/))&&(n=E(this.entities.quoted)||E(/^[\w-]+/))?e=[t,r,n.toCSS?n.toCSS():n].join(""):e=t;if(!E("]"))return;if(e)return"["+e+"]"},block:function(){var e;if(E("{")&&(e=E(this.primary))&&E("}"))return e},ruleset:function(){var e=[],n,r,u,a;g(),t.dumpLineNumbers&&(a=A(o,s,t));while(n=E(this.selector)){e.push(n),E(this.comment);if(!E(","))break;E(this.comment)}if(e.length>0&&(r=E(this.block))){var f=new i.Ruleset(e,r,t.strictImports);return t.dumpLineNumbers&&(f.debugInfo=a),f}l=o,y()},rule:function(){var e,t,n=s.charAt(o),r,a;g();if(n==="."||n==="#"||n==="&")return;if(e=E(this.variable)||E(this.property)){e.charAt(0)!="@"&&(a=/^([^@+\/'"*`(;{}-]*);/.exec(c[u]))?(o+=a[0].length-1,t=new i.Anonymous(a[1])):e==="font"?t=E(this.font):t=E(this.value),r=E(this.important);if(t&&E(this.end))return new i.Rule(e,t,r,f);l=o,y()}},"import":function(){var e,n,r=o;g();var s=E(/^@import(?:-(once))?\s+/);if(s&&(e=E(this.entities.quoted)||E(this.entities.url))){n=E(this.mediaFeatures);if(E(";"))return new i.Import(e,m,n,s[1]==="once",r,t.rootpath)}y()},mediaFeature:function(){var e,t,n=[];do if(e=E(this.entities.keyword))n.push(e);else if(E("(")){t=E(this.property),e=E(this.entity);if(!E(")"))return null;if(t&&e)n.push(new i.Paren(new i.Rule(t,e,null,o,!0)));else{if(!e)return null;n.push(new i.Paren(e))}}while(e);if(n.length>0)return new i.Expression(n)},mediaFeatures:function(){var e,t=[];do if(e=E(this.mediaFeature)){t.push(e);if(!E(","))break}else if(e=E(this.entities.variable)){t.push(e);if(!E(","))break}while(e);return t.length>0?t:null},media:function(){var e,n,r,u;t.dumpLineNumbers&&(u=A(o,s,t));if(E(/^@media/)){e=E(this.mediaFeatures);if(n=E(this.block))return r=new i.Media(n,e),t.dumpLineNumbers&&(r.debugInfo=u),r}},directive:function(){var e,n,r,u,a,f,l,c,h,p;if(s.charAt(o)!=="@")return;if(n=E(this["import"])||E(this.media))return n;g(),e=E(/^@[a-z-]+/);if(!e)return;l=e,e.charAt(1)=="-"&&e.indexOf("-",2)>0&&(l="@"+e.slice(e.indexOf("-",2)+1));switch(l){case"@font-face":c=!0;break;case"@viewport":case"@top-left":case"@top-left-corner":case"@top-center":case"@top-right":case"@top-right-corner":case"@bottom-left":case"@bottom-left-corner":case"@bottom-center":case"@bottom-right":case"@bottom-right-corner":case"@left-top":case"@left-middle":case"@left-bottom":case"@right-top":case"@right-middle":case"@right-bottom":c=!0;break;case"@page":case"@document":case"@supports":case"@keyframes":c=!0,h=!0;break;case"@namespace":p=!0}h&&(e+=" "+(E(/^[^{]+/)||"").trim());if(c){if(r=E(this.block))return new i.Directive(e,r)}else if((n=p?E(this.expression):E(this.entity))&&E(";")){var d=new i.Directive(e,n);return t.dumpLineNumbers&&(d.debugInfo=A(o,s,t)),d}y()},font:function(){var e=[],t=[],n,r,s,o;while(o=E(this.shorthand)||E(this.entity))t.push(o);e.push(new i.Expression(t));if(E(","))while(o=E(this.expression)){e.push(o);if(!E(","))break}return new i.Value(e)},value:function(){var e,t=[],n;while(e=E(this.expression)){t.push(e);if(!E(","))break}if(t.length>0)return new i.Value(t)},important:function(){if(s.charAt(o)==="!")return E(/^! *important/)},sub:function(){var e;if(E("(")&&(e=E(this.expression))&&E(")"))return e},multiplication:function(){var e,t,n,r;if(e=E(this.operand)){while(!N(/^\/[*\/]/)&&(n=E("/")||E("*"))&&(t=E(this.operand)))r=new i.Operation(n,[r||e,t]);return r||e}},addition:function(){var e,t,n,r;if(e=E(this.multiplication)){while((n=E(/^[-+]\s+/)||!w(s.charAt(o-1))&&(E("+")||E("-")))&&(t=E(this.multiplication)))r=new i.Operation(n,[r||e,t]);return r||e}},conditions:function(){var e,t,n=o,r;if(e=E(this.condition)){while(E(",")&&(t=E(this.condition)))r=new i.Condition("or",r||e,t,n);return r||e}},condition:function(){var e,t,n,r,s=o,u=!1;E(/^not/)&&(u=!0),x("(");if(e=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))return(r=E(/^(?:>=|=<|[<=>])/))?(t=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))?n=new i.Condition(r,e,t,s,u):T("expected expression"):n=new i.Condition("=",e,new i.Keyword("true"),s,u),x(")"),E(/^and/)?new i.Condition("and",n,E(this.condition)):n},operand:function(){var e,t=s.charAt(o+1);s.charAt(o)==="-"&&(t==="@"||t==="(")&&(e=E("-"));var n=E(this.sub)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.variable)||E(this.entities.call);return e?new i.Operation("*",[new i.Dimension(-1),n]):n},expression:function(){var e,t,n=[],r;while(e=E(this.addition)||E(this.entity))n.push(e);if(n.length>0)return new i.Expression(n)},property:function(){var e;if(e=E(/^(\*?-?[_a-z0-9-]+)\s*:/))return e[1]}}}};if(r.mode==="browser"||r.mode==="rhino")r.Parser.importer=function(e,t,n,r){!/^([a-z-]+:)?\//.test(e)&&t.length>0&&(e=t[0]+e),w({href:e,title:e,type:r.mime,contents:r.contents,files:r.files,rootpath:r.rootpath,entryPath:r.entryPath,relativeUrls:r.relativeUrls},function(e,i,s,o,u,a){e&&typeof r.errback=="function"?r.errback.call(null,a,t,n,r):n.call(null,e,i,a)},!0)};(function(e){function t(t){return e.functions.hsla(t.h,t.s,t.l,t.a)}function n(t,n){return t instanceof e.Dimension&&t.unit=="%"?parseFloat(t.value*n/100):r(t)}function r(t){if(t instanceof e.Dimension)return parseFloat(t.unit=="%"?t.value/100:t.value);if(typeof t=="number")return t;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function i(e){return Math.min(1,Math.max(0,e))}e.functions={rgb:function(e,t,n){return this.rgba(e,t,n,1)},rgba:function(t,i,s,o){var u=[t,i,s].map(function(e){return n(e,256)});return o=r(o),new e.Color(u,o)},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,n,i){function u(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(s-o)*e*6:e*2<1?s:e*3<2?o+(s-o)*(2/3-e)*6:o}e=r(e)%360/360,t=r(t),n=r(n),i=r(i);var s=n<=.5?n*(t+1):n+t-n*t,o=n*2-s;return this.rgba(u(e+1/3)*255,u(e)*255,u(e-1/3)*255,i)},hsv:function(e,t,n){return this.hsva(e,t,n,1)},hsva:function(e,t,n,i){e=r(e)%360/360*360,t=r(t),n=r(n),i=r(i);var s,o;s=Math.floor(e/60%6),o=e/60-s;var u=[n,n*(1-t),n*(1-o*t),n*(1-(1-o)*t)],a=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(u[a[s][0]]*255,u[a[s][1]]*255,u[a[s][2]]*255,i)},hue:function(t){return new e.Dimension(Math.round(t.toHSL().h))},saturation:function(t){return new e.Dimension(Math.round(t.toHSL().s*100),"%")},lightness:function(t){return new e.Dimension(Math.round(t.toHSL().l*100),"%")},red:function(t){return new e.Dimension(t.rgb[0])},green:function(t){return new e.Dimension(t.rgb[1])},blue:function(t){return new e.Dimension(t.rgb[2])},alpha:function(t){return new e.Dimension(t.toHSL().a)},luma:function(t){return new e.Dimension(Math.round((.2126*(t.rgb[0]/255)+.7152*(t.rgb[1]/255)+.0722*(t.rgb[2]/255))*t.alpha*100),"%")},saturate:function(e,n){var r=e.toHSL();return r.s+=n.value/100,r.s=i(r.s),t(r)},desaturate:function(e,n){var r=e.toHSL();return r.s-=n.value/100,r.s=i(r.s),t(r)},lighten:function(e,n){var r=e.toHSL();return r.l+=n.value/100,r.l=i(r.l),t(r)},darken:function(e,n){var r=e.toHSL();return r.l-=n.value/100,r.l=i(r.l),t(r)},fadein:function(e,n){var r=e.toHSL();return r.a+=n.value/100,r.a=i(r.a),t(r)},fadeout:function(e,n){var r=e.toHSL();return r.a-=n.value/100,r.a=i(r.a),t(r)},fade:function(e,n){var r=e.toHSL();return r.a=n.value/100,r.a=i(r.a),t(r)},spin:function(e,n){var r=e.toHSL(),i=(r.h+n.value)%360;return r.h=i<0?360+i:i,t(r)},mix:function(t,n,r){r||(r=new e.Dimension(50));var i=r.value/100,s=i*2-1,o=t.toHSL().a-n.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[t.rgb[0]*u+n.rgb[0]*a,t.rgb[1]*u+n.rgb[1]*a,t.rgb[2]*u+n.rgb[2]*a],l=t.alpha*i+n.alpha*(1-i);return new e.Color(f,l)},greyscale:function(t){return this.desaturate(t,new e.Dimension(100))},contrast:function(e,t,n,r){return e.rgb?(typeof n=="undefined"&&(n=this.rgba(255,255,255,1)),typeof t=="undefined"&&(t=this.rgba(0,0,0,1)),typeof r=="undefined"?r=.43:r=r.value,(.2126*(e.rgb[0]/255)+.7152*(e.rgb[1]/255)+.0722*(e.rgb[2]/255))*e.alpha<r?n:t):null},e:function(t){return new e.Anonymous(t instanceof e.JavaScript?t.evaluated:t)},escape:function(t){return new e.Anonymous(encodeURI(t.value).replace(/=/g,"%3D").replace(/:/g,"%3A").replace(/#/g,"%23").replace(/;/g,"%3B").replace(/\(/g,"%28").replace(/\)/g,"%29"))},"%":function(t){var n=Array.prototype.slice.call(arguments,1),r=t.value;for(var i=0;i<n.length;i++)r=r.replace(/%[sda]/i,function(e){var t=e.match(/s/i)?n[i].value:n[i].toCSS();return e.match(/[A-Z]$/)?encodeURIComponent(t):t});return r=r.replace(/%%/g,"%"),new e.Quoted('"'+r+'"',r)},unit:function(t,n){return new e.Dimension(t.value,n?n.toCSS():"")},round:function(e,t){var n=typeof t=="undefined"?0:t.value;return this._math(function(e){return e.toFixed(n)},e)},ceil:function(e){return this._math(Math.ceil,e)},floor:function(e){return this._math(Math.floor,e)},_math:function(t,n){if(n instanceof e.Dimension)return new e.Dimension(t(parseFloat(n.value)),n.unit);if(typeof n=="number")return t(n);throw{type:"Argument",message:"argument must be a number"}},argb:function(t){return new e.Anonymous(t.toARGB())},percentage:function(t){return new e.Dimension(t.value*100,"%")},color:function(t){if(t instanceof e.Quoted)return new e.Color(t.value.slice(1));throw{type:"Argument",message:"argument must be a string"}},iscolor:function(t){return this._isa(t,e.Color)},isnumber:function(t){return this._isa(t,e.Dimension)},isstring:function(t){return this._isa(t,e.Quoted)},iskeyword:function(t){return this._isa(t,e.Keyword)},isurl:function(t){return this._isa(t,e.URL)},ispixel:function(t){return t instanceof e.Dimension&&t.unit==="px"?e.True:e.False},ispercentage:function(t){return t instanceof e.Dimension&&t.unit==="%"?e.True:e.False},isem:function(t){return t instanceof e.Dimension&&t.unit==="em"?e.True:e.False},_isa:function(t,n){return t instanceof n?e.True:e.False},multiply:function(e,t){var n=e.rgb[0]*t.rgb[0]/255,r=e.rgb[1]*t.rgb[1]/255,i=e.rgb[2]*t.rgb[2]/255;return this.rgb(n,r,i)},screen:function(e,t){var n=255-(255-e.rgb[0])*(255-t.rgb[0])/255,r=255-(255-e.rgb[1])*(255-t.rgb[1])/255,i=255-(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},overlay:function(e,t){var n=e.rgb[0]<128?2*e.rgb[0]*t.rgb[0]/255:255-2*(255-e.rgb[0])*(255-t.rgb[0])/255,r=e.rgb[1]<128?2*e.rgb[1]*t.rgb[1]/255:255-2*(255-e.rgb[1])*(255-t.rgb[1])/255,i=e.rgb[2]<128?2*e.rgb[2]*t.rgb[2]/255:255-2*(255-e.rgb[2])*(255-t.rgb[2])/255;return this.rgb(n,r,i)},softlight:function(e,t){var n=t.rgb[0]*e.rgb[0]/255,r=n+e.rgb[0]*(255-(255-e.rgb[0])*(255-t.rgb[0])/255-n)/255;n=t.rgb[1]*e.rgb[1]/255;var i=n+e.rgb[1]*(255-(255-e.rgb[1])*(255-t.rgb[1])/255-n)/255;n=t.rgb[2]*e.rgb[2]/255;var s=n+e.rgb[2]*(255-(255-e.rgb[2])*(255-t.rgb[2])/255-n)/255;return this.rgb(r,i,s)},hardlight:function(e,t){var n=t.rgb[0]<128?2*t.rgb[0]*e.rgb[0]/255:255-2*(255-t.rgb[0])*(255-e.rgb[0])/255,r=t.rgb[1]<128?2*t.rgb[1]*e.rgb[1]/255:255-2*(255-t.rgb[1])*(255-e.rgb[1])/255,i=t.rgb[2]<128?2*t.rgb[2]*e.rgb[2]/255:255-2*(255-t.rgb[2])*(255-e.rgb[2])/255;return this.rgb(n,r,i)},difference:function(e,t){var n=Math.abs(e.rgb[0]-t.rgb[0]),r=Math.abs(e.rgb[1]-t.rgb[1]),i=Math.abs(e.rgb[2]-t.rgb[2]);return this.rgb(n,r,i)},exclusion:function(e,t){var n=e.rgb[0]+t.rgb[0]*(255-e.rgb[0]-e.rgb[0])/255,r=e.rgb[1]+t.rgb[1]*(255-e.rgb[1]-e.rgb[1])/255,i=e.rgb[2]+t.rgb[2]*(255-e.rgb[2]-e.rgb[2])/255;return this.rgb(n,r,i)},average:function(e,t){var n=(e.rgb[0]+t.rgb[0])/2,r=(e.rgb[1]+t.rgb[1])/2,i=(e.rgb[2]+t.rgb[2])/2;return this.rgb(n,r,i)},negation:function(e,t){var n=255-Math.abs(255-t.rgb[0]-e.rgb[0]),r=255-Math.abs(255-t.rgb[1]-e.rgb[1]),i=255-Math.abs(255-t.rgb[2]-e.rgb[2]);return this.rgb(n,r,i)},tint:function(e,t){return this.mix(this.rgb(255,255,255),e,t)},shade:function(e,t){return this.mix(this.rgb(0,0,0),e,t)}}})(n("./tree")),function(e){e.colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen
+:"#9acd32"}}(n("./tree")),function(e){e.Alpha=function(e){this.value=e},e.Alpha.prototype={toCSS:function(){return"alpha(opacity="+(this.value.toCSS?this.value.toCSS():this.value)+")"},eval:function(e){return this.value.eval&&(this.value=this.value.eval(e)),this}}}(n("../tree")),function(e){e.Anonymous=function(e){this.value=e.value||e},e.Anonymous.prototype={toCSS:function(){return this.value},eval:function(){return this},compare:function(e){if(!e.toCSS)return-1;var t=this.toCSS(),n=e.toCSS();return t===n?0:t<n?-1:1}}}(n("../tree")),function(e){e.Assignment=function(e,t){this.key=e,this.value=t},e.Assignment.prototype={toCSS:function(){return this.key+"="+(this.value.toCSS?this.value.toCSS():this.value)},eval:function(t){return this.value.eval?new e.Assignment(this.key,this.value.eval(t)):this}}}(n("../tree")),function(e){e.Call=function(e,t,n,r){this.name=e,this.args=t,this.index=n,this.filename=r},e.Call.prototype={eval:function(t){var n=this.args.map(function(e){return e.eval(t)}),r;if(this.name in e.functions)try{r=e.functions[this.name].apply(e.functions,n);if(r!=null)return r}catch(i){throw{type:i.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(i.message?": "+i.message:""),index:this.index,filename:this.filename}}return new e.Anonymous(this.name+"("+n.map(function(e){return e.toCSS(t)}).join(", ")+")")},toCSS:function(e){return this.eval(e).toCSS()}}}(n("../tree")),function(e){e.Color=function(e,t){Array.isArray(e)?this.rgb=e:e.length==6?this.rgb=e.match(/.{2}/g).map(function(e){return parseInt(e,16)}):this.rgb=e.split("").map(function(e){return parseInt(e+e,16)}),this.alpha=typeof t=="number"?t:1},e.Color.prototype={eval:function(){return this},toCSS:function(){return this.alpha<1?"rgba("+this.rgb.map(function(e){return Math.round(e)}).concat(this.alpha).join(", ")+")":"#"+this.rgb.map(function(e){return e=Math.round(e),e=(e>255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},operate:function(t,n){var r=[];n instanceof e.Color||(n=n.toColor());for(var i=0;i<3;i++)r[i]=e.operate(t,this.rgb[i],n.rgb[i]);return new e.Color(r,this.alpha+n.alpha)},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t<n?6:0);break;case t:o=(n-e)/f+2;break;case n:o=(e-t)/f+4}o/=6}return{h:o*360,s:u,l:a,a:r}},toARGB:function(){var e=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+e.map(function(e){return e=Math.round(e),e=(e>255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},compare:function(e){return e.rgb?e.rgb[0]===this.rgb[0]&&e.rgb[1]===this.rgb[1]&&e.rgb[2]===this.rgb[2]&&e.alpha===this.alpha?0:-1:-1}}}(n("../tree")),function(e){e.Comment=function(e,t){this.value=e,this.silent=!!t},e.Comment.prototype={toCSS:function(e){return e.compress?"":this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Condition=function(e,t,n,r,i){this.op=e.trim(),this.lvalue=t,this.rvalue=n,this.index=r,this.negate=i},e.Condition.prototype.eval=function(e){var t=this.lvalue.eval(e),n=this.rvalue.eval(e),r=this.index,i,i=function(e){switch(e){case"and":return t&&n;case"or":return t||n;default:if(t.compare)i=t.compare(n);else{if(!n.compare)throw{type:"Type",message:"Unable to perform comparison",index:r};i=n.compare(t)}switch(i){case-1:return e==="<"||e==="=<";case 0:return e==="="||e===">="||e==="=<";case 1:return e===">"||e===">="}}}(this.op);return this.negate?!i:i}}(n("../tree")),function(e){e.Dimension=function(e,t){this.value=parseFloat(e),this.unit=t||null},e.Dimension.prototype={eval:function(){return this},toColor:function(){return new e.Color([this.value,this.value,this.value])},toCSS:function(){var e=this.value+this.unit;return e},operate:function(t,n){return new e.Dimension(e.operate(t,this.value,n.value),this.unit||n.unit)},compare:function(t){return t instanceof e.Dimension?t.value>this.value?-1:t.value<this.value?1:t.unit&&this.unit!==t.unit?-1:0:-1}}}(n("../tree")),function(e){e.Directive=function(t,n){this.name=t,Array.isArray(n)?(this.ruleset=new e.Ruleset([],n),this.ruleset.allowImports=!0):this.value=n},e.Directive.prototype={toCSS:function(e,t){return this.ruleset?(this.ruleset.root=!0,this.name+(t.compress?"{":" {\n  ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n  ")+(t.compress?"}":"\n}\n")):this.name+" "+this.value.toCSS()+";\n"},eval:function(t){var n=this;return this.ruleset&&(t.frames.unshift(this),n=new e.Directive(this.name),n.ruleset=this.ruleset.eval(t),t.frames.shift()),n},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)}}}(n("../tree")),function(e){e.Element=function(t,n,r){this.combinator=t instanceof e.Combinator?t:new e.Combinator(t),typeof n=="string"?this.value=n.trim():n?this.value=n:this.value="",this.index=r},e.Element.prototype.eval=function(t){return new e.Element(this.combinator,this.value.eval?this.value.eval(t):this.value,this.index)},e.Element.prototype.toCSS=function(e){var t=this.value.toCSS?this.value.toCSS(e):this.value;return t==""&&this.combinator.value.charAt(0)=="&"?"":this.combinator.toCSS(e||{})+t},e.Combinator=function(e){e===" "?this.value=" ":this.value=e?e.trim():""},e.Combinator.prototype.toCSS=function(e){return{"":""," ":" ",":":" :","+":e.compress?"+":" + ","~":e.compress?"~":" ~ ",">":e.compress?">":" > ","|":e.compress?"|":" | "}[this.value]}}(n("../tree")),function(e){e.Expression=function(e){this.value=e},e.Expression.prototype={eval:function(t){return this.value.length>1?new e.Expression(this.value.map(function(e){return e.eval(t)})):this.value.length===1?this.value[0].eval(t):this},toCSS:function(e){return this.value.map(function(t){return t.toCSS?t.toCSS(e):""}).join(" ")}}}(n("../tree")),function(e){e.Import=function(t,n,r,i,s,o){var u=this;this.once=i,this.index=s,this._path=t,this.features=r&&new e.Value(r),this.rootpath=o,t instanceof e.Quoted?this.path=/(\.[a-z]*$)|([\?;].*)$/.test(t.value)?t.value:t.value+".less":this.path=t.value.value||t.value,this.css=/css([\?;].*)?$/.test(this.path),this.css||n.push(this.path,function(t,n,r){t&&(t.index=s),r&&u.once&&(u.skip=r),u.root=n||new e.Ruleset([],[])})},e.Import.prototype={toCSS:function(e){var t=this.features?" "+this.features.toCSS(e):"";return this.css?(typeof this._path.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(this._path.value)&&(this._path.value=this.rootpath+this._path.value),"@import "+this._path.toCSS()+t+";\n"):""},eval:function(t){var n,r=this.features&&this.features.eval(t);return this.skip?[]:this.css?this:(n=new e.Ruleset([],this.root.rules.slice(0)),n.evalImports(t),this.features?new e.Media(n.rules,this.features.value):n.rules)}}}(n("../tree")),function(e){e.JavaScript=function(e,t,n){this.escaped=n,this.expression=e,this.index=t},e.JavaScript.prototype={eval:function(t){var n,r=this,i={},s=this.expression.replace(/@\{([\w-]+)\}/g,function(n,i){return e.jsify((new e.Variable("@"+i,r.index)).eval(t))});try{s=new Function("return ("+s+")")}catch(o){throw{message:"JavaScript evaluation error: `"+s+"`",index:this.index}}for(var u in t.frames[0].variables())i[u.slice(1)]={value:t.frames[0].variables()[u].value,toJS:function(){return this.value.eval(t).toCSS()}};try{n=s.call(i)}catch(o){throw{message:"JavaScript evaluation error: '"+o.name+": "+o.message+"'",index:this.index}}return typeof n=="string"?new e.Quoted('"'+n+'"',n,this.escaped,this.index):Array.isArray(n)?new e.Anonymous(n.join(", ")):new e.Anonymous(n)}}}(n("../tree")),function(e){e.Keyword=function(e){this.value=e},e.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value},compare:function(t){return t instanceof e.Keyword?t.value===this.value?0:1:-1}},e.True=new e.Keyword("true"),e.False=new e.Keyword("false")}(n("../tree")),function(e){e.Media=function(t,n){var r=this.emptySelectors();this.features=new e.Value(n),this.ruleset=new e.Ruleset(r,t),this.ruleset.allowImports=!0},e.Media.prototype={toCSS:function(e,t){var n=this.features.toCSS(t);return this.ruleset.root=e.length===0||e[0].multiMedia,"@media "+n+(t.compress?"{":" {\n  ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n  ")+(t.compress?"}":"\n}\n")},eval:function(t){t.mediaBlocks||(t.mediaBlocks=[],t.mediaPath=[]);var n=new e.Media([],[]);return this.debugInfo&&(this.ruleset.debugInfo=this.debugInfo,n.debugInfo=this.debugInfo),n.features=this.features.eval(t),t.mediaPath.push(n),t.mediaBlocks.push(n),t.frames.unshift(this.ruleset),n.ruleset=this.ruleset.eval(t),t.frames.shift(),t.mediaPath.pop(),t.mediaPath.length===0?n.evalTop(t):n.evalNested(t)},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)},emptySelectors:function(){var t=new e.Element("","&",0);return[new e.Selector([t])]},evalTop:function(t){var n=this;if(t.mediaBlocks.length>1){var r=this.emptySelectors();n=new e.Ruleset(r,t.mediaBlocks),n.multiMedia=!0}return delete t.mediaBlocks,delete t.mediaPath,n},evalNested:function(t){var n,r,i=t.mediaPath.concat([this]);for(n=0;n<i.length;n++)r=i[n].features instanceof e.Value?i[n].features.value:i[n].features,i[n]=Array.isArray(r)?r:[r];return this.features=new e.Value(this.permute(i).map(function(t){t=t.map(function(t){return t.toCSS?t:new e.Anonymous(t)});for(n=t.length-1;n>0;n--)t.splice(n,0,new e.Anonymous("and"));return new e.Expression(t)})),new e.Ruleset([],[])},permute:function(e){if(e.length===0)return[];if(e.length===1)return e[0];var t=[],n=this.permute(e.slice(1));for(var r=0;r<n.length;r++)for(var i=0;i<e[0].length;i++)t.push([e[0][i]].concat(n[r]));return t},bubbleSelectors:function(t){this.ruleset=new e.Ruleset(t.slice(0),[this.ruleset])}}}(n("../tree")),function(e){e.mixin={},e.mixin.Call=function(t,n,r,i,s){this.selector=new e.Selector(t),this.arguments=n,this.index=r,this.filename=i,this.important=s},e.mixin.Call.prototype={eval:function(t){var n,r,i,s=[],o=!1,u,a,f,l,c;i=this.arguments&&this.arguments.map(function(e){return{name:e.name,value:e.value.eval(t)}});for(u=0;u<t.frames.length;u++)if((n=t.frames[u].find(this.selector)).length>0){c=!0;for(a=0;a<n.length;a++){r=n[a],l=!1;for(f=0;f<t.frames.length;f++)if(!(r instanceof e.mixin.Definition)&&r===(t.frames[f].originalRuleset||t.frames[f])){l=!0;break}if(l)continue;if(r.matchArgs(i,t)){if(!r.matchCondition||r.matchCondition(i,t))try{Array.prototype.push.apply(s,r.eval(t,i,this.important).rules)}catch(h){throw{message:h.message,index:this.index,filename:this.filename,stack:h.stack}}o=!0}}if(o)return s}throw c?{type:"Runtime",message:"No matching definition was found for `"+this.selector.toCSS().trim()+"("+(i?i.map(function(e){var t="";return e.name&&(t+=e.name+":"),e.value.toCSS?t+=e.value.toCSS():t+="???",t}).join(", "):"")+")`",index:this.index,filename:this.filename}:{type:"Name",message:this.selector.toCSS().trim()+" is undefined",index:this.index,filename:this.filename}}},e.mixin.Definition=function(t,n,r,i,s){this.name=t,this.selectors=[new e.Selector([new e.Element(null,t)])],this.params=n,this.condition=i,this.variadic=s,this.arity=n.length,this.rules=r,this._lookups={},this.required=n.reduce(function(e,t){return!t.name||t.name&&!t.value?e+1:e},0),this.parent=e.Ruleset.prototype,this.frames=[]},e.mixin.Definition.prototype={toCSS:function(){return""},variable:function(e){return this.parent.variable.call(this,e)},variables:function(){return this.parent.variables.call(this)},find:function(){return this.parent.find.apply(this,arguments)},rulesets:function(){return this.parent.rulesets.apply(this)},evalParams:function(t,n,r,i){var s=new e.Ruleset(null,[]),o,u,a=this.params.slice(0),f,l,c,h,p,d;if(r){r=r.slice(0);for(f=0;f<r.length;f++){u=r[f];if(h=u&&u.name){p=!1;for(l=0;l<a.length;l++)if(!i[l]&&h===a[l].name){i[l]=u.value.eval(t),s.rules.unshift(new e.Rule(h,u.value.eval(t))),p=!0;break}if(p){r.splice(f,1),f--;continue}throw{type:"Runtime",message:"Named argument for "+this.name+" "+r[f].name+" not found"}}}}d=0;for(f=0;f<a.length;f++){if(i[f])continue;u=r&&r[d];if(h=a[f].name)if(a[f].variadic&&r){o=[];for(l=d;l<r.length;l++)o.push(r[l].value.eval(t));s.rules.unshift(new e.Rule(h,(new e.Expression(o)).eval(t)))}else{c=u&&u.value;if(c)c=c.eval(t);else{if(!a[f].value)throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+r.length+" for "+this.arity+")"};c=a[f].value.eval(n)}s.rules.unshift(new e.Rule(h,c)),i[f]=c}if(a[f].variadic&&r)for(l=d;l<r.length;l++)i[l]=r[l].value.eval(t);d++}return s},eval:function(t,n,r){var i=[],s=this.frames.concat(t.frames),o=this.evalParams(t,{frames:s},n,i),u,a,f,l;return o.rules.unshift(new e.Rule("@arguments",(new e.Expression(i)).eval(t))),a=r?this.parent.makeImportant.apply(this).rules:this.rules.slice(0),l=(new e.Ruleset(null,a)).eval({frames:[this,o].concat(s)}),l.originalRuleset=this,l},matchCondition:function(e,t){return this.condition&&!this.condition.eval({frames:[this.evalParams(t,{frames:this.frames.concat(t.frames)},e,[])].concat(t.frames)})?!1:!0},matchArgs:function(e,t){var n=e&&e.length||0,r,i;if(!this.variadic){if(n<this.required)return!1;if(n>this.params.length)return!1;if(this.required>0&&n>this.params.length)return!1}r=Math.min(n,this.arity);for(var s=0;s<r;s++)if(!this.params[s].name&&!this.params[s].variadic&&e[s].value.eval(t).toCSS()!=this.params[s].value.eval(t).toCSS())return!1;return!0}}}(n("../tree")),function(e){e.Operation=function(e,t){this.op=e.trim(),this.operands=t},e.Operation.prototype.eval=function(t){var n=this.operands[0].eval(t),r=this.operands[1].eval(t),i;if(n instanceof e.Dimension&&r instanceof e.Color){if(this.op!=="*"&&this.op!=="+")throw{name:"OperationError",message:"Can't substract or divide a color from a number"};i=r,r=n,n=i}if(!n.operate)throw{name:"OperationError",message:"Operation on an invalid type"};return n.operate(this.op,r)},e.operate=function(e,t,n){switch(e){case"+":return t+n;case"-":return t-n;case"*":return t*n;case"/":return t/n}}}(n("../tree")),function(e){e.Paren=function(e){this.value=e},e.Paren.prototype={toCSS:function(e){return"("+this.value.toCSS(e)+")"},eval:function(t){return new e.Paren(this.value.eval(t))}}}(n("../tree")),function(e){e.Quoted=function(e,t,n,r){this.escaped=n,this.value=t||"",this.quote=e.charAt(0),this.index=r},e.Quoted.prototype={toCSS:function(){return this.escaped?this.value:this.quote+this.value+this.quote},eval:function(t){var n=this,r=this.value.replace(/`([^`]+)`/g,function(r,i){return(new e.JavaScript(i,n.index,!0)).eval(t).value}).replace(/@\{([\w-]+)\}/g,function(r,i){var s=(new e.Variable("@"+i,n.index)).eval(t);return s instanceof e.Quoted?s.value:s.toCSS()});return new e.Quoted(this.quote+r+this.quote,r,this.escaped,this.index)},compare:function(e){if(!e.toCSS)return-1;var t=this.toCSS(),n=e.toCSS();return t===n?0:t<n?-1:1}}}(n("../tree")),function(e){e.Ratio=function(e){this.value=e},e.Ratio.prototype={toCSS:function(e){return this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Rule=function(t,n,r,i,s){this.name=t,this.value=n instanceof e.Value?n:new e.Value([n]),this.important=r?" "+r.trim():"",this.index=i,this.inline=s||!1,t.charAt(0)==="@"?this.variable=!0:this.variable=!1},e.Rule.prototype.toCSS=function(e){return this.variable?"":this.name+(e.compress?":":": ")+this.value.toCSS(e)+this.important+(this.inline?"":";")},e.Rule.prototype.eval=function(t){return new e.Rule(this.name,this.value.eval(t),this.important,this.index,this.inline)},e.Rule.prototype.makeImportant=function(){return new e.Rule(this.name,this.value,"!important",this.index,this.inline)},e.Shorthand=function(e,t){this.a=e,this.b=t},e.Shorthand.prototype={toCSS:function(e){return this.a.toCSS(e)+"/"+this.b.toCSS(e)},eval:function(){return this}}}(n("../tree")),function(e){e.Ruleset=function(e,t,n){this.selectors=e,this.rules=t,this._lookups={},this.strictImports=n},e.Ruleset.prototype={eval:function(t){var n=this.selectors&&this.selectors.map(function(e){return e.eval(t)}),r=new e.Ruleset(n,this.rules.slice(0),this.strictImports),i;r.originalRuleset=this,r.root=this.root,r.allowImports=this.allowImports,this.debugInfo&&(r.debugInfo=this.debugInfo),t.frames.unshift(r),(r.root||r.allowImports||!r.strictImports)&&r.evalImports(t);for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.mixin.Definition&&(r.rules[s].frames=t.frames.slice(0));var o=t.mediaBlocks&&t.mediaBlocks.length||0;for(var s=0;s<r.rules.length;s++)r.rules[s]instanceof e.mixin.Call&&(i=r.rules[s].eval(t),r.rules.splice.apply(r.rules,[s,1].concat(i)),s+=i.length-1,r.resetCache());for(var s=0,u;s<r.rules.length;s++)u=r.rules[s],u instanceof e.mixin.Definition||(r.rules[s]=u.eval?u.eval(t):u);t.frames.shift();if(t.mediaBlocks)for(var s=o;s<t.mediaBlocks.length;s++)t.mediaBlocks[s].bubbleSelectors(n);return r},evalImports:function(t){var n,r;for(n=0;n<this.rules.length;n++)this.rules[n]instanceof e.Import&&(r=this.rules[n].eval(t),typeof r.length=="number"?(this.rules.splice.apply(this.rules,[n,1].concat(r)),n+=r.length-1):this.rules.splice(n,1,r),this.resetCache())},makeImportant:function(){return new e.Ruleset(this.selectors,this.rules.map(function(e){return e.makeImportant?e.makeImportant():e}),this.strictImports)},matchArgs:function(e){return!e||e.length===0},resetCache:function(){this._rulesets=null,this._variables=null,this._lookups={}},variables:function(){return this._variables?this._variables:this._variables=this.rules.reduce(function(t,n){return n instanceof e.Rule&&n.variable===!0&&(t[n.name]=n),t},{})},variable:function(e){return this.variables()[e]},rulesets:function(){return this._rulesets?this._rulesets:this._rulesets=this.rules.filter(function(t){return t instanceof e.Ruleset||t instanceof e.mixin.Definition})},find:function(t,n){n=n||this;var r=[],i,s,o=t.toCSS();return o in this._lookups?this._lookups[o]:(this.rulesets().forEach(function(i){if(i!==n)for(var o=0;o<i.selectors.length;o++)if(s=t.match(i.selectors[o])){t.elements.length>i.selectors[o].elements.length?Array.prototype.push.apply(r,i.find(new e.Selector(t.elements.slice(1)),n)):r.push(i);break}}),this._lookups[o]=r)},toCSS:function(t,n){var r=[],i=[],s=[],o=[],u=[],a,f,l;this.root||this.joinSelectors(u,t,this.selectors);for(var c=0;c<this.rules.length;c++){l=this.rules[c];if(l.rules||l instanceof e.Media)o.push(l.toCSS(u,n));else if(l instanceof e.Directive){var h=l.toCSS(u,n);if(l.name==="@charset"){if(n.charset){l.debugInfo&&(o.push(e.debugInfo(n,l)),o.push((new e.Comment("/* "+h.replace(/\n/g,"")+" */\n")).toCSS(n)));continue}n.charset=!0}o.push(h)}else l instanceof e.Comment?l.silent||(this.root?o.push(l.toCSS(n)):i.push(l.toCSS(n))):l.toCSS&&!l.variable?i.push(l.toCSS(n)):l.value&&!l.variable&&i.push(l.value.toString())}o=o.join("");if(this.root)r.push(i.join(n.compress?"":"\n"));else if(i.length>0){f=e.debugInfo(n,this),a=u.map(function(e){return e.map(function(e){return e.toCSS(n)}).join("").trim()}).join(n.compress?",":",\n");for(var c=i.length-1;c>=0;c--)s.indexOf(i[c])===-1&&s.unshift(i[c]);i=s,r.push(f+a+(n.compress?"{":" {\n  ")+i.join(n.compress?"":"\n  ")+(n.compress?"}":"\n}\n"))}return r.push(o),r.join("")+(n.compress?"\n":"")},joinSelectors:function(e,t,n){for(var r=0;r<n.length;r++)this.joinSelector(e,t,n[r])},joinSelector:function(t,n,r){var i,s,o,u,a,f,l,c,h,p,d,v,m,g,y;for(i=0;i<r.elements.length;i++)f=r.elements[i],f.value==="&"&&(u=!0);if(!u){if(n.length>0)for(i=0;i<n.length;i++)t.push(n[i].concat(r));else t.push([r]);return}g=[],a=[[]];for(i=0;i<r.elements.length;i++){f=r.elements[i];if(f.value!=="&")g.push(f);else{y=[],g.length>0&&this.mergeElementsOnToSelectors(g,a);for(s=0;s<a.length;s++){l=a[s];if(n.length==0)l.length>0&&(l[0].elements=l[0].elements.slice(0),l[0].elements.push(new e.Element(f.combinator,"",0))),y.push(l);else for(o=0;o<n.length;o++)c=n[o],h=[],p=[],v=!0,l.length>0?(h=l.slice(0),m=h.pop(),d=new e.Selector(m.elements.slice(0)),v=!1):d=new e.Selector([]),c.length>1&&(p=p.concat(c.slice(1))),c.length>0&&(v=!1,d.elements.push(new e.Element(f.combinator,c[0].elements[0].value,0)),d.elements=d.elements.concat(c[0].elements.slice(1))),v||h.push(d),h=h.concat(p),y.push(h)}a=y,g=[]}}g.length>0&&this.mergeElementsOnToSelectors(g,a);for(i=0;i<a.length;i++)t.push(a[i])},mergeElementsOnToSelectors:function(t,n){var r,i;if(n.length==0){n.push([new e.Selector(t)]);return}for(r=0;r<n.length;r++)i=n[r],i.length>0?i[i.length-1]=new e.Selector(i[i.length-1].elements.concat(t)):i.push(new e.Selector(t))}}}(n("../tree")),function(e){e.Selector=function(e){this.elements=e},e.Selector.prototype.match=function(e){var t=this.elements,n=t.length,r,i,s,o;r=e.elements.slice(e.elements.length&&e.elements[0].value==="&"?1:0),i=r.length,s=Math.min(n,i);if(i===0||n<i)return!1;for(o=0;o<s;o++)if(t[o].value!==r[o].value)return!1;return!0},e.Selector.prototype.eval=function(t){return new e.Selector(this.elements.map(function(e){return e.eval(t)}))},e.Selector.prototype.toCSS=function(e){return this._css?this._css:(this.elements[0].combinator.value===""?this._css=" ":this._css="",this._css+=this.elements.map(function(t){return typeof t=="string"?" "+t.trim():t.toCSS(e)}).join(""),this._css)}}(n("../tree")),function(e){e.UnicodeDescriptor=function(e){this.value=e},e.UnicodeDescriptor.prototype={toCSS:function(e){return this.value},eval:function(){return this}}}(n("../tree")),function(e){e.URL=function(e,t){this.value=e,this.rootpath=t},e.URL.prototype={toCSS:function(){return"url("+this.value.toCSS()+")"},eval:function(t){var n=this.value.eval(t),r;return typeof n.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(n.value)&&(r=this.rootpath,n.quote||(r=r.replace(/[\(\)'"\s]/g,function(e){return"\\"+e})),n.value=r+n.value),new e.URL(n,this.rootpath)}}}(n("../tree")),function(e){e.Value=function(e){this.value=e,this.is="value"},e.Value.prototype={eval:function(t){return this.value.length===1?this.value[0].eval(t):new e.Value(this.value.map(function(e){return e.eval(t)}))},toCSS:function(e){return this.value.map(function(t){return t.toCSS(e)}).join(e.compress?",":", ")}}}(n("../tree")),function(e){e.Variable=function(e,t,n){this.name=e,this.index=t,this.file=n},e.Variable.prototype={eval:function(t){var n,r,i=this.name;i.indexOf("@@")==0&&(i="@"+(new e.Variable(i.slice(1))).eval(t).value);if(this.evaluating)throw{type:"Name",message:"Recursive variable definition for "+i,filename:this.file,index:this.index};this.evaluating=!0;if(n=e.find(t.frames,function(e){if(r=e.variable(i))return r.value.eval(t)}))return this.evaluating=!1,n;throw{type:"Name",message:"variable "+i+" is undefined",filename:this.file,index:this.index}}}}(n("../tree")),function(e){e.debugInfo=function(t,n){var r="";if(t.dumpLineNumbers&&!t.compress)switch(t.dumpLineNumbers){case"comments":r=e.debugInfo.asComment(n);break;case"mediaquery":r=e.debugInfo.asMediaQuery(n);break;case"all":r=e.debugInfo.asComment(n)+e.debugInfo.asMediaQuery(n)}return r},e.debugInfo.asComment=function(e){return"/* line "+e.debugInfo.lineNumber+", "+e.debugInfo.fileName+" */\n"},e.debugInfo.asMediaQuery=function(e){return"@media -sass-debug-info{filename{font-family:"+("file://"+e.debugInfo.fileName).replace(/[\/:.]/g,"\\$&")+"}line{font-family:\\00003"+e.debugInfo.lineNumber+"}}\n"},e.find=function(e,t){for(var n=0,r;n<e.length;n++)if(r=t.call(e,e[n]))return r;return null},e.jsify=function(e){return Array.isArray(e.value)&&e.value.length>1?"["+e.value.map(function(e){return e.toCSS(!1)}).join(", ")+"]":e.toCSS(!1)}}(n("./tree"));var o=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);r.env=r.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||o?"development":"production"),r.async=r.async||!1,r.fileAsync=r.fileAsync||!1,r.poll=r.poll||(o?1e3:1500);if(r.functions)for(var u in r.functions)r.tree.functions[u]=r.functions[u];var a=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);a&&(r.dumpLineNumbers=a[1]),r.watch=function(){return r.watchMode||(r.env="development",f()),this.watchMode=!0},r.unwatch=function(){return clearInterval(r.watchTimer),this.watchMode=!1},/!watch/.test(location.hash)&&r.watch();var l=null;if(r.env!="development")try{l=typeof e.localStorage=="undefined"?null:e.localStorage}catch(c){}var h=document.getElementsByTagName("link"),p=/^text\/(x-)?less$/;r.sheets=[];for(var d=0;d<h.length;d++)(h[d].rel==="stylesheet/less"||h[d].rel.match(/stylesheet/)&&h[d].type.match(p))&&r.sheets.push(h[d]);var v="";r.modifyVars=function(e){var t=v;for(name in e)t+=(name.slice(0,1)==="@"?"":"@")+name+": "+(e[name].slice(-1)===";"?e[name]:e[name]+";");(new r.Parser).parse(t,function(e,t){S(t.toCSS(),r.sheets[r.sheets.length-1])})},r.refresh=function(e){var t,n;t=n=new Date,g(function(e,r,i,s,o){o.local?C("loading "+s.href+" from cache."):(C("parsed "+s.href+" successfully."),S(r.toCSS(),s,o.lastModified)),C("css for "+s.href+" generated in "+(new Date-n)+"ms"),o.remaining===0&&C("css generated in "+(new Date-t)+"ms"),n=new Date},e),m()},r.refreshStyles=m,r.refresh(r.env==="development"),typeof define=="function"&&define.amd&&define("less",[],function(){return r})})(window);
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/js/one-topology.js b/opendaylight/web/root/src/main/resources/js/one-topology.js
new file mode 100644 (file)
index 0000000..df36ea4
--- /dev/null
@@ -0,0 +1,218 @@
+/** COMMON **/
+var labelType, useGradients, nativeTextSupport, animate;
+
+(function() {
+       var ua = navigator.userAgent,
+       iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
+       typeOfCanvas = typeof HTMLCanvasElement,
+       nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
+       textSupport = nativeCanvasSupport 
+       && (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
+       //I'm setting this based on the fact that ExCanvas provides text support for IE
+       //and that as of today iPhone/iPad current text support is lame
+       labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML';
+       nativeTextSupport = labelType == 'Native';
+       useGradients = nativeCanvasSupport;
+       animate = !(iStuff || !nativeCanvasSupport);
+})();
+
+/** TOPOLOGY **/
+one.topology = {};
+
+one.topology.option = {
+       navigation : function(enable, panning, zooming) {
+               var option = {};
+               option["enable"] = enable;
+               option["panning"] = panning;
+               option["zooming"] = zooming;
+               return option;
+       },
+       node : function(overridable, color, height, dim) {
+               var option = {};
+               option["overridable"] = overridable;
+               option["color"] = color;
+               option["height"] = height;
+               option["dim"] = dim;
+               return option;
+       },
+       edge : function(overridable, color, lineWidth, epsilon) {
+               var option = {};
+               option["overridable"] = overridable;
+               option["color"] = color;
+               option["lineWidth"] = lineWidth;
+               if (epsilon != undefined)
+                       option["epsilon"] = epsilon;
+               return option;
+       },
+       label : function(style, node) {
+               var marginTop, minWidth;
+               if (node.data["$type"] == "swtch") {
+                       marginTop = "42px";
+                       minWidth = "65px";
+               } else if (node.data["$type"] == "host") {
+                       marginTop = "48px";
+                       minWidth = "";
+               } else if (node.data["$type"].indexOf("monitor") == 0) {
+                       marginTop = "52px";
+                       minWidth = "";
+               }
+               style.marginTop = marginTop;
+               style.minWidth = minWidth;
+               style.background = "rgba(68,68,68,0.7)";
+               style.borderRadius = "4px";
+               style.color = "#fff";
+               style.cursor = "default";
+       }
+};
+
+one.topology.init = function(json) {
+       if (json.length == 0) {
+               $div = $(document.createElement('div'));
+               $img = $(document.createElement('div'));
+               $img.css('height', '128px');
+               $img.css('width', '128px');
+               $img.css('background-image', 'url(/img/topology_view_1033_128.png)');
+               $img.css('clear', 'both');
+               $img.css('margin', '0 auto');
+               $p = $(document.createElement('p'));
+               $p.addClass('text-center');
+               $p.addClass('text-info');
+               $p.css('width', '100%');
+               $p.css('padding', '10px 0');
+               $p.css('cursor', 'default');
+               $p.append('No Network Elements Connected');
+               $div.css('position', 'absolute');
+               $div.css('top', '25%');
+               $div.css('margin', '0 auto');
+               $div.css('width', '100%');
+               $div.css('text-align', 'center');
+               $div.append($img).append($p);
+               $("#topology").append($div);
+               return false;
+       }
+       one.topology.graph = new $jit.MultiTopology({
+               injectInto: 'topology',
+               Navigation: one.topology.option.navigation(true, 'avoid nodes', 10),
+               Node: one.topology.option.node(true, '#444', 25, 27),
+               Edge: one.topology.option.edge(true, '23A4FF', 1.5),
+               Tips: {
+                       enable: true,
+                       type: 'Native',
+                       onShow: function(tip, node) {
+                               if (node.name != undefined)
+                                       tip.innerHTML = "";
+                               //tipsOnShow(tip, node);
+                       }
+               },
+               Events: {
+                       enable: true,
+                       type: 'Native',
+                       onMouseEnter: function(node, eventInfo, e) {
+                               if (node.id != undefined) // if node
+                                       one.topology.graph.canvas.getElement().style.cursor = 'move';
+                               else if (eventInfo.edge != undefined && eventInfo.edge.nodeTo.data["$type"] == "swtch" && eventInfo.edge.nodeFrom.data["$type"] == "swtch")
+                                       one.topology.graph.canvas.getElement().style.cursor = 'pointer';
+                       },
+                       onMouseLeave: function(node, eventInfo, e) {
+                               one.topology.graph.canvas.getElement().style.cursor = '';
+                       },
+                       //Update node positions when dragged
+                       onDragMove: function(node, eventInfo, e) {
+                               var pos = eventInfo.getPos();
+                               node.pos.setc(pos.x, pos.y);
+                               one.topology.graph.plot();
+                               one.topology.graph.canvas.getElement().style.cursor = 'crosshair';
+                       },
+                       //Implement the same handler for touchscreens
+                       onTouchMove: function(node, eventInfo, e) {
+                               $jit.util.event.stop(e); //stop default touchmove event
+                               this.onDragMove(node, eventInfo, e);
+                       },
+                       onDragEnd: function(node, eventInfo, e) {
+                               var ps = eventInfo.getPos();
+                               var did = node.id;
+                               var data = {};
+                               data['x'] = ps.x;
+                               data['y'] = ps.y;
+                               $.post('/one/topology/node/' + did, data);
+                       },
+                       onClick: function(node, eventInfo, e) {
+                               return false;
+                       }
+               },
+               iterations: 200,
+               levelDistance: 130,
+               onCreateLabel: function(domElement, node){
+                       var nameContainer = document.createElement('span'),
+                       closeButton = document.createElement('span'),
+                       style = nameContainer.style;
+                       nameContainer.className = 'name';
+                       var nodeDesc = node.data["$desc"];
+                       if (nodeDesc == "None" || nodeDesc == "" || nodeDesc == "undefined" || nodeDesc == undefined) {
+                               nameContainer.innerHTML = "<small>"+node.name+"</small>";
+                       } else {
+                               nameContainer.innerHTML = nodeDesc;
+                       }
+                       domElement.appendChild(nameContainer);
+                       style.fontSize = "1.0em";
+                       style.color = "#000";
+                       style.fontWeight = "bold";
+                       style.width = "100%";
+                       style.padding = "1.5px 4px";
+                       style.display = "block";
+
+                       one.topology.option.label(style, node);
+               },
+               onPlaceLabel: function(domElement, node){
+                       var style = domElement.style;
+                       var left = parseInt(style.left);
+                       var top = parseInt(style.top);
+                       var w = domElement.offsetWidth;
+                       style.left = (left - w / 2) + 'px';
+                       style.top = (top - 15) + 'px';
+                       style.display = '';
+                       style.minWidth = "28px";
+                       style.width = "auto";
+                       style.height = "28px";
+                       style.textAlign = "center";
+               }
+       });
+       one.topology.graph.loadJSON(json);
+       // compute positions incrementally and animate.
+       one.topology.graph.computeIncremental({
+               iter: 40,
+               property: 'end',
+               onStep: function(perc){
+                       console.log(perc + '% loaded');
+               },
+               onComplete: function(){
+                       for (var idx in one.topology.graph.graph.nodes) {
+                               var node = one.topology.graph.graph.nodes[idx];
+                               if(node.getData("x") && node.getData("y")) {
+                                       var x = node.getData("x");
+                                       var y = node.getData("y");
+                                       node.setPos(new $jit.Complex(x, y), "end");
+                               }
+                       }
+                       console.log('done');
+                       one.topology.graph.animate({
+                               modes: ['linear'],
+                               transition: $jit.Trans.Elastic.easeOut,
+                               duration: 0
+                       });
+               }
+       });
+       one.topology.graph.canvas.setZoom(0.8,0.8);
+}
+
+one.topology.update = function() {
+       $('#topology').empty();
+       $.getJSON(one.global.remoteAddress+"one/topology/visual.json", function(data) {
+               one.topology.init(data);
+       });
+}
+
+/** INIT */
+$.getJSON(one.global.remoteAddress+"one/topology/visual.json", function(data) {
+       one.topology.init(data);
+});
\ No newline at end of file
diff --git a/opendaylight/web/root/src/main/resources/js/one.js b/opendaylight/web/root/src/main/resources/js/one.js
new file mode 100644 (file)
index 0000000..d582a9d
--- /dev/null
@@ -0,0 +1,717 @@
+// global
+var one = {
+       // global variables
+       global : {
+               remoteAddress : "/"
+       },
+       role : null
+}
+
+// one ui library
+one.lib = {};
+
+// registry
+one.lib.registry = {};
+
+/** DASHLET */
+one.lib.dashlet = {
+       empty : function($dashlet) {
+               $dashlet.empty();
+       },
+       append : function($dashlet, $content) {
+               $dashlet.append($content);
+       },
+       header : function(header) {
+               var $h4 = $(document.createElement('h4'));
+               $h4.text(header);
+               return $h4;
+       },
+       list : function(list) {
+               var $ul = $(document.createElement('ul'));
+               $(list).each(function(index, value) {
+                       var $li = $(document.createElement('li'));
+                       $li.append(value);
+                       $ul.append($li);
+               });
+               return $ul;
+       },
+       button : {
+               config : function(name, id, type, size) {
+                       var button = {};
+                       button['name'] = name;
+                       button['id'] = id;
+                       button['type'] = type;
+                       button['size'] = size;
+                       return button;
+               },
+               single : function(name, id, type, size) {
+                       var buttonList = [];
+                       var button = one.lib.dashlet.button.config(name, id, type, size);
+                       buttonList.push(button);
+                       return buttonList;
+               },
+               button : function(buttonList) {
+                       var $buttonGroup = $(document.createElement('div'));
+                       $buttonGroup.addClass("btn-group");
+                       $(buttonList).each(function(index, value) {
+                               var $button = $(document.createElement('button'));
+                               $button.text(value.name);
+                               $button.addClass('btn');
+                               $button.addClass(value['type']);
+                               $button.addClass(value['size']);
+                               if(!(typeof value.id === 'undefined')) {
+                                       $button.attr('id', value.id);
+                               }
+                               $buttonGroup.append($button);
+                       });
+                       return $buttonGroup;
+               }
+       },
+       table : {
+               table : function(classes, id) {
+                       var $table = $(document.createElement('table'));
+                       $table.addClass("table");
+                       $(classes).each(function(index, value) {
+                               $table.addClass(value);
+                       });
+                       if (!(typeof id === 'undefined'))
+                               $table.attr("id", id);
+                       return $table;
+               },
+               header : function(headers) {
+                       var $thead = $(document.createElement('thead'));
+                       var $tr = $(document.createElement('tr'));
+                       $(headers).each(function(index, value) {
+                               $th = $(document.createElement('th'));
+                               $th.append(value);
+                               $tr.append($th);
+                       });
+                       $thead.append($tr);
+                       return $thead;
+               },
+               body : function(body, thead) {
+                       var $tbody = $(document.createElement('tbody'));
+                       // if empty
+                       if (body.length == 0 && !(typeof thead === 'undefined')) {
+                               var $tr = $(document.createElement('tr'));
+                               var $td = $(document.createElement('td'));
+                               $td.attr("colspan", thead.length);
+                               $td.text("No data available");
+                               $td.addClass("empty");
+                               $tr.append($td);
+                               $tbody.append($tr);
+                               return $tbody;
+                       }
+                       // else, populate as usual
+                       $(body).each(function(index, value) {
+                               var $tr = $(document.createElement('tr'));
+                               // data-id
+                               if(value['id'] != undefined) {
+                                       $tr.attr('data-id', value['id']);
+                               }
+                               // add classes
+                               $(value["type"]).each(function(index, value) {
+                                       $tr.addClass(value);
+                               });
+                               // add entries
+                               $(value["entry"]).each(function(index, value) {
+                                       var $td = $(document.createElement('td'));
+                                       $td.append(value);
+                                       $tr.append($td);
+                               });
+                               $tbody.append($tr);
+                       });
+                       return $tbody;
+               }
+       },
+       description : function(description, horizontal) {
+               var $dl = $(document.createElement('dl'));
+               if(horizontal == true) {
+                       $dl.addClass("dl-horizontal");
+               }
+               $(description).each(function(index, value) {
+                       var $dt = $(document.createElement('dt'));
+                       $dt.text(value.dt);
+                       var $dd = $(document.createElement('dd'));
+                       $dd.text(value.dd);
+                       $dl.append($dt).append($dd);
+               });
+               return $dl;
+       }
+}
+
+/** MODAL */
+one.lib.modal = {
+       // clone default modal
+       clone : function(id) {
+               var $clone = $("#modal").clone(true);
+               $clone.attr("id", id);
+               return $clone;
+       },
+       // populate modal
+       populate : function($modal, header, $body, footer) {
+               var $h3 = $modal.find("h3");
+               $h3.text(header);
+
+               var $modalBody = $modal.find(".modal-body");
+               $modalBody.append($body);
+
+               $(footer).each(function(index, value) {
+                       $modal.find(".modal-footer").append(value);
+               });
+       },
+       // clone and populate modal
+       spawn : function(id, header, $body, footer) {
+               var $modal = one.lib.modal.clone(id);
+               one.lib.modal.populate($modal, header, $body, footer);
+               return $modal;
+       },
+       // empty modal
+       empty : function($modal) {
+               $modal.find("h3").empty();
+               $modal.find(".modal-body").empty();
+               $modal.find(".modal-footer").empty();
+       },
+       // injection
+       inject : {
+               body : function($modal, $body) {
+                       $modal.find(".modal-body").empty();
+                       $modal.find(".modal-body").append($body);
+               }
+       }
+}
+
+/** FORM */
+one.lib.form = {
+       // create select-option form element
+       select : {
+               create : function(options, multiple) {
+                       // assert - auto assign
+                       if(options == undefined) options = {};
+
+                       var $select = $(document.createElement('select'));
+                       if (multiple == true) {
+                               $select.attr("multiple", "multiple");
+                       }
+                       var optionArray = one.lib.form.select.options(options);
+                       $(optionArray).each(function(index, value) {
+                               $select.append(value);
+                       });
+                       return $select;
+               },
+               options : function(options) {
+                       var result = [];
+                       $.each(options, function(key, value) {
+                               var $option = $(document.createElement('option'));
+                               $option.attr("value", key);
+                               $option.text(value);
+                               result.push($option);
+                       });
+                       return result;
+               },
+               empty : function($select) {
+                       $select.empty();
+               },
+               inject : function($select, options) {
+                       $select.empty();
+                       var options = one.lib.form.select.options(options);
+                       $select.append(options);
+               },
+               prepend : function($select, options) {
+                       var options = one.lib.form.select.options(options);
+                       $select.prepend(options);
+               },
+               bubble : function($select, bubble) {
+                       $($select.find("option")).each(function(index, value) {
+                               if( $(value).attr("value") == bubble ) {
+                                       var option = $(value);
+                                       $(value).remove();
+                                       $select.prepend(option);
+                                       return;
+                               }
+                       });
+               }
+       },
+       // create legend form element
+       legend : function(name) {
+               var $legend = $(document.createElement('legend'));
+               $legend.text(name);
+               return $legend;
+       },
+       // create label form element
+       label : function(name) {
+               var $label = $(document.createElement('label'));
+               $label.text(name);
+               return $label;
+       },
+       // create help block form element
+       help : function(help) {
+               var $help = $(document.createElement('span'));
+               $help.text(help);
+               $help.addClass("help-block");
+               return $help;
+       },
+       // create generic text input
+       input : function(placeholder) {
+               var $input = $(document.createElement('input'));
+               $input.attr('type', 'text');
+               $input.attr('placeholder', placeholder);
+               return $input;
+       }
+}
+
+/** NAV */
+one.lib.nav = {
+       unfocus : function($nav) {
+               $($nav.find("li")).each(function(index, value) {
+                       $(value).removeClass("active");
+               });
+       }
+}
+
+/** ALERT */
+one.lib.alert = function(alert) {
+       $("#alert p").text(alert);
+       $("#alert").hide();
+       $("#alert").slideToggle();
+       clearTimeout(one.lib.registry.alert);
+       one.lib.registry.alert = setTimeout(function() {
+               $("#alert").slideUp();
+       }, 8000);
+}
+
+/* 
+       INITIALIZATION
+       executable JS code starts here
+*/
+//ONE root page
+one.main = {};
+
+one.main.constants = {
+    address : {
+        menu : "/web.json",
+        prefix : "/one",
+        save : "/save"
+    }
+}
+
+one.main.menu = {
+    load : function() {
+        one.main.menu.ajax(function(data) {
+            // reparse the ajax data
+            var result = one.main.menu.data.menu(data);
+            // transform into list to append to menu
+            var $div = one.main.menu.menu(result);
+            // append to menu
+            $("#menu .nav").append($div.children());
+            // binding all menu items
+            var $menu = $("#menu .nav a");
+            $menu.click(function() {
+                var href = $(this).attr('href').substring(1);
+                one.main.page.load(href);
+                var $li = $(this).parent();
+                // reset all other active
+                $menu.each(function(index, value) {
+                       $(value).parent().removeClass('active');
+                });
+                $li.addClass('active');
+            });
+            // reset or go to first menu item by default
+            var currentLocation = location.hash;
+            if (data[currentLocation.substring(1)] == undefined) {
+               $($menu[0]).click();
+            } else {
+               $menu.each(function(index, value) {
+                       var menuLocation = $(value).attr('href');
+                       if (currentLocation == menuLocation) {
+                               $($menu[index]).click();
+                               return;
+                       }
+               });
+            }
+        });
+    },
+    menu : function(result) {
+        var $div = $(document.createElement('div'));
+        $(result).each(function(index, value) {
+            if( value != undefined) {
+                var $li = $(document.createElement('li'));
+                var $a = $(document.createElement('a'));
+                $a.text(value['name']);
+                $a.attr('href', '#'+value['id']);
+                $li.append($a);
+                $div.append($li);
+            }
+        });
+        return $div;
+    },
+    ajax : function(successCallback) {
+        $.getJSON(one.main.constants.address.menu, function(data) {
+            successCallback(data);
+        });
+    },
+    data : {
+        menu : function(data) {
+            var result = [];
+            $.each(data, function(key, value) {
+                var order = value['order'];
+                if (order >= 0) {
+                       var name = value['name'];
+                       var entry = {
+                           'name' : name,
+                           'id' : key
+                       };
+                       result[order] = entry;
+                }
+            });
+            return result;
+        }
+    }
+}
+
+one.main.page = {
+    load : function(page) {
+        // clear page related
+        delete one.f;
+        $('.dashlet', '#main').empty();
+        $('.nav', '#main').empty();
+        // fetch page's js
+        $.getScript(one.main.constants.address.prefix+"/"+page+"/js/page.js");
+        
+        $.ajaxSetup({
+               data : {
+                       'x-page-url' : page
+               }
+        });
+    },
+    dashlet : function($nav, dashlet) {
+        var $li = $(document.createElement('li'));
+        var $a = $(document.createElement('a'));
+        $a.text(dashlet.name);
+        $a.attr('id', dashlet.id);
+        $a.attr('href', '#');
+        $li.append($a);
+        $nav.append($li);
+    }
+}
+
+one.main.admin = {
+    id : {
+        modal : {
+            main : "one_main_admin_id_modal_main",
+            close : "one_main_admin_id_modal_close",
+            user : "one_main_admin_id_modal_user",
+            add : {
+                user : "one_main_admin_id_modal_add_user",
+                close : "one_main_admin_id_modal_add_close",
+                form : {
+                    name : "one_main_admin_id_modal_add_form_name",
+                    role : "one_main_admin_id_modal_add_form_role",
+                    password : "one_main_admin_id_modal_add_form_password"
+                }
+            },
+            remove : {
+                user : "one_main_admin_id_modal_remove_user",
+                close : "one_main_admin_id_modal_remove_close"
+            }
+        },
+        add : {
+            user : "one_main_admin_id_add_user"
+        }
+    },
+    address : {
+        root : "/admin",
+        users : "/users"
+    },
+    modal : {
+        initialize : function(callback) {
+            var h3 = "Welcome "+$('#admin').text();
+            var footer = one.main.admin.modal.footer();
+            var $modal = one.lib.modal.spawn(one.main.admin.id.modal.main, h3, '', footer);
+            
+            // close binding
+            $('#'+one.main.admin.id.modal.close, $modal).click(function() {
+                $modal.modal('hide');
+            });
+            
+            // body inject
+            one.main.admin.ajax.users(function($body) {
+                one.lib.modal.inject.body($modal, $body);
+            });
+            
+            // modal show callback
+            callback($modal);
+        },
+        footer : function() {
+            var footer = [];
+            
+            var closeButton = one.lib.dashlet.button.single("Close", one.main.admin.id.modal.close, "", "");
+            var $closeButton = one.lib.dashlet.button.button(closeButton);
+            footer.push($closeButton);
+            
+            return footer;
+        }
+    },
+    ajax : {
+        users : function(callback) {
+            $.getJSON(one.main.admin.address.root+one.main.admin.address.users, function(data) {
+                var body = one.main.admin.data.users(data);
+                var $body = one.main.admin.body.users(body);
+                callback($body);
+            });
+        }
+    },
+    data : {
+        users : function(data) {
+            var body = [];
+            $(data).each(function(index, value) {
+                var tr = {};
+                var entry = [];
+                entry.push(value['user']);
+                entry.push(value['role']);
+                tr['entry'] = entry;
+                tr['id'] = value['user'];
+                body.push(tr);
+            });
+            return body;
+        }
+    },
+    body : {
+        users : function(body) {
+            var $div = $(document.createElement('div'));
+            var $h5 = $(document.createElement('h5'));
+            $h5.append("Manage Users");
+            var attributes = ["table-striped", "table-bordered", "table-hover", "table-cursor"];
+            var $table = one.lib.dashlet.table.table(attributes);
+            var headers = ["User", "Role"];
+            var $thead = one.lib.dashlet.table.header(headers);
+            var $tbody = one.lib.dashlet.table.body(body);
+            $table.append($thead).append($tbody);
+            
+            // bind table
+            if (one.role < 2) {
+                   $table.find('tr').click(function() {
+                       var id = $(this).data('id');
+                       one.main.admin.remove.modal.initialize(id);
+                   });
+            }
+            
+            // append to div
+            $div.append($h5).append($table);
+            
+            if (one.role < 2) {
+                   var addUserButton = one.lib.dashlet.button.single("Add User", one.main.admin.id.add.user, "btn-primary", "btn-mini");
+                   var $addUserButton = one.lib.dashlet.button.button(addUserButton);
+                   $div.append($addUserButton);
+                   
+                   // add user binding
+                   $addUserButton.click(function() {
+                       one.main.admin.add.modal.initialize();
+                   });
+            }
+            
+            return $div;
+        }
+    },
+    remove : {
+        modal : {
+            initialize : function(id) {
+                var h3 = "Remove User";
+                var footer = one.main.admin.remove.footer();
+                var $body = one.main.admin.remove.body();
+                var $modal = one.lib.modal.spawn(one.main.admin.id.modal.user, h3, $body, footer);
+                
+                // close binding
+                $('#'+one.main.admin.id.modal.remove.close, $modal).click(function() {
+                    $modal.modal('hide');
+                });
+                
+                // remove binding
+                $('#'+one.main.admin.id.modal.remove.user, $modal).click(function() {
+                    one.main.admin.remove.modal.ajax(id, function(result) {
+                        if (result == 'Success') {
+                            $modal.modal('hide');
+                            // body inject
+                            var $admin = $('#'+one.main.admin.id.modal.main);
+                            one.main.admin.ajax.users(function($body) {
+                                one.lib.modal.inject.body($admin, $body);
+                            });
+                        } else alert("Failed to remove user: "+result);
+                    });
+                });
+                
+                $modal.modal();
+            },
+            ajax : function(id, callback) {
+                $.post(one.main.admin.address.root+one.main.admin.address.users+'/'+id, function(data) {
+                    callback(data);
+                });
+            },
+        },
+        
+        footer : function() {
+            var footer = [];
+            
+            var removeButton = one.lib.dashlet.button.single("Remove User", one.main.admin.id.modal.remove.user, "btn-danger", "");
+            var $removeButton = one.lib.dashlet.button.button(removeButton);
+            footer.push($removeButton);
+            
+            var closeButton = one.lib.dashlet.button.single("Close", one.main.admin.id.modal.remove.close, "", "");
+            var $closeButton = one.lib.dashlet.button.button(closeButton);
+            footer.push($closeButton);
+            
+            return footer;
+        },
+        body : function() {
+            var $p = $(document.createElement('p'));
+            $p.append("Remove user?");
+            return $p;
+        },
+    },
+    add : {
+        modal : {
+            initialize : function() {
+                var h3 = "Add User";
+                var footer = one.main.admin.add.footer();
+                var $body = one.main.admin.add.body();
+                var $modal = one.lib.modal.spawn(one.main.admin.id.modal.user, h3, $body, footer);
+                
+                // close binding
+                $('#'+one.main.admin.id.modal.add.close, $modal).click(function() {
+                    $modal.modal('hide');
+                });
+                
+                // add binding
+                $('#'+one.main.admin.id.modal.add.user, $modal).click(function() {
+                    one.main.admin.add.modal.add($modal, function(result) {
+                        if(result == 'Success') {
+                            $modal.modal('hide');
+                            // body inject
+                            var $admin = $('#'+one.main.admin.id.modal.main);
+                            one.main.admin.ajax.users(function($body) {
+                                one.lib.modal.inject.body($admin, $body);
+                            });
+                        } else alert("Failed to add user: "+result);
+                    });
+                });
+                
+                $modal.modal();
+            },
+            add : function($modal, callback) {
+                var user = {};
+                user['user'] = $modal.find('#'+one.main.admin.id.modal.add.form.name).val();
+                user['password'] = $modal.find('#'+one.main.admin.id.modal.add.form.password).val();
+                user['role'] = $modal.find('#'+one.main.admin.id.modal.add.form.role).find('option:selected').attr('value');
+                
+                var resource = {};
+                resource['json'] = JSON.stringify(user);
+                resource['action'] = 'add'
+                
+                one.main.admin.add.modal.ajax(resource, callback);
+            },
+            ajax : function(data, callback) {
+                $.post(one.main.admin.address.root+one.main.admin.address.users, data, function(data) {
+                    callback(data);
+                });
+            }
+        },
+        body : function() {
+            var $form = $(document.createElement('form'));
+            var $fieldset = $(document.createElement('fieldset'));
+            // user
+            var $label = one.lib.form.label('Username');
+            var $input = one.lib.form.input('Username');
+            $input.attr('id', one.main.admin.id.modal.add.form.name);
+            $fieldset.append($label).append($input);
+            // password
+            var $label = one.lib.form.label('Password');
+            var $input = one.lib.form.input('Password');
+            $input.attr('id', one.main.admin.id.modal.add.form.password);
+            $input.attr('type', 'password');
+            $fieldset.append($label).append($input);
+            // roles
+            var $label = one.lib.form.label('Roles');
+            var options = {
+                "Network-Admin" : "Network Administrator",
+                "Network-Operator" : "Network Operator"
+            };
+            var $select = one.lib.form.select.create(options);
+            $select.attr('id', one.main.admin.id.modal.add.form.role);
+            $fieldset.append($label).append($select);
+            $form.append($fieldset);
+            return $form;
+        },
+        footer : function() {
+            var footer = [];
+            
+            var addButton = one.lib.dashlet.button.single("Add User", one.main.admin.id.modal.add.user, "btn-primary", "");
+            var $addButton = one.lib.dashlet.button.button(addButton);
+            footer.push($addButton);
+            
+            var closeButton = one.lib.dashlet.button.single("Close", one.main.admin.id.modal.add.close, "", "");
+            var $closeButton = one.lib.dashlet.button.button(closeButton);
+            footer.push($closeButton);
+            
+            return footer;
+        }
+    }
+}
+
+one.main.dashlet = {
+       left : {
+               top : $("#left-top .dashlet"),
+               bottom : $("#left-bottom .dashlet")
+       },
+       right : {
+               bottom : $("#right-bottom .dashlet")
+       }
+}
+
+/** BOOTSTRAP */
+$(".modal").on('hidden', function() {
+    $(this).remove();
+});
+
+$("#alert .close").click(function() {
+       $("#alert").hide();
+});
+
+/** INIT */
+
+// parse role
+one.role = $('#admin').data('role');
+
+// user admin
+$("#admin").click(function() {
+    one.main.admin.modal.initialize(function($modal) {
+        $modal.modal();
+    });
+});
+
+// save
+$("#save").click(function() {
+       $.post(one.main.constants.address.save, function(data) {
+               if (data == "Success") {
+                       one.lib.alert("Configuration Saved");
+               } else {
+                       one.lib.alert("Unable to save configuration: "+data);
+               }
+       });
+});
+
+// logout
+$("#logout").click(function() {
+       location.href = "/logout";
+});
+
+$.ajaxSetup({
+    complete : function(xhr,textStatus) {
+        var page = xhr.getResponseHeader('X-Page-Location');
+        if(page == '/login') {
+            location.href = '/login';
+        }
+    }
+});
+
+/** MAIN PAGE LOAD */
+one.main.menu.load();
\ No newline at end of file
diff --git a/opendaylight/web/topology/pom.xml b/opendaylight/web/topology/pom.xml
new file mode 100644 (file)
index 0000000..f453358
--- /dev/null
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../../commons/opendaylight</relativePath>
+       </parent>
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>topology.web</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Export-Package>
+                                               </Export-Package>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.containermanager,
+                                                       org.opendaylight.controller.sal.authorization,
+                                                       org.opendaylight.controller.sal.packet.address,
+                                                       org.opendaylight.controller.sal.action,
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.switchmanager,
+                                                       org.opendaylight.controller.topologymanager,
+                                                       org.opendaylight.controller.usermanager,
+                                                       org.opendaylight.controller.web,
+                                                       com.google.gson,
+                                                       edu.uci.ics.jung.algorithms.layout,
+                                                       edu.uci.ics.jung.graph,
+                                                       javax.annotation,
+                                                       javax.naming,
+                                                       javax.servlet,
+                                                       javax.servlet.annotation,
+                                                       javax.servlet.http,
+                                                       javax.servlet.jsp,
+                                                       javax.servlet.jsp.el,
+                                                       javax.servlet.jsp.jstl.core,
+                                                       javax.servlet.jsp.jstl.fmt,
+                                                       javax.servlet.jsp.jstl.tlv,
+                                                       javax.servlet.jsp.tagext,
+                                                       javax.servlet.resources,
+                                                       javax.xml.parsers,
+                                                       javax.xml.transform,
+                                                       org.apache.commons.logging,
+                                                       org.apache.taglibs.standard.functions,
+                                                       org.apache.taglibs.standard.resources,
+                                                       org.apache.taglibs.standard.tag.common.core,
+                                                       org.apache.taglibs.standard.tag.common.fmt,
+                                                       org.apache.taglibs.standard.tag.rt.core,
+                                                       org.apache.taglibs.standard.tag.rt.fmt,
+                                                       org.apache.taglibs.standard.tei,
+                                                       org.apache.taglibs.standard.tlv,
+                                                       org.codehaus.jackson,
+                                                       org.codehaus.jackson.annotate,
+                                                       org.codehaus.jackson.map,
+                                                       org.codehaus.jackson.map.annotate,
+                                                       org.osgi.framework,
+                                                       org.slf4j,
+                                                       org.springframework.beans,
+                                                       org.springframework.beans.factory.xml,
+                                                       org.springframework.context.config,
+                                                       org.springframework.stereotype,
+                                                       org.springframework.util,
+                                                       org.springframework.web,
+                                                       org.springframework.web.bind.annotation,
+                                                       org.springframework.web.servlet,
+                                                       org.springframework.web.servlet.config,
+                                                       org.springframework.web.servlet.view,
+
+                                                       org.springframework.web.filter,
+                                                       org.springframework.web.context,
+                                                       org.springframework.security.core,
+                                                       org.springframework.security.core.userdetails,
+                                                       org.springframework.security.core.authority,
+                                                       org.springframework.security.core.context,
+                                                       org.springframework.security.authentication,
+                                                       org.springframework.security.config,
+                                                       org.springframework.security.config.authentication,
+                                                       org.springframework.security.taglibs.authz,
+                                                       org.springframework.security.web,
+                                                       org.springframework.security.web.context,
+                                                       org.springframework.security.web.authentication,
+                                                       org.springframework.security.web.authentication.www,
+                                                       org.springframework.security.provisioning,
+                                                       org.springframework.security.web.util,
+                                                       org.springframework.security.web.authentication.rememberme,
+                                                       org.springframework.security.web.authentication.logout,
+                                                       org.springframework.dao
+                                               </Import-Package>
+                                               <Web-ContextPath>/one/topology</Web-ContextPath>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller.thirdparty</groupId>
+                       <artifactId>net.sf.jung2</artifactId>
+                       <version>2.0.1-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>containermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>switchmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>topologymanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>web</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/web/topology/src/main/java/org/opendaylight/controller/topology/web/Topology.java b/opendaylight/web/topology/src/main/java/org/opendaylight/controller/topology/web/Topology.java
new file mode 100644 (file)
index 0000000..aeffad2
--- /dev/null
@@ -0,0 +1,444 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.topology.web;
+
+import java.awt.Dimension;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.core.Bandwidth;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+
+@Controller
+@RequestMapping("/")
+public class Topology {
+
+    protected Map<String, Map<String, Object>> cache = new HashMap<String, Map<String, Object>>();
+    protected Map<String, Map<String, Object>> stage;
+    protected Map<String, Map<String, Object>> newNodes;
+    protected Integer nodeHash = null;
+    protected Integer hostHash = null;
+    protected Integer nodeSingleHash = null;
+    protected Integer nodeConfigurationHash = null;
+    
+    /**
+     * Topology of nodes and hosts in the network in JSON format.
+     * 
+     * Mainly intended for consumption by the visual topology.
+     * 
+     * @return - JSON output for visual topology
+     */
+    @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
+    @ResponseBody
+    public Collection<Map<String, Object>> getLinkData() {
+        ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
+                .getInstance(ITopologyManager.class, "default", this);
+        if (topologyManager == null) return null;
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, "default", this);
+        if (switchManager == null) return null;
+        
+        Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
+        Map<Node, Set<NodeConnector>> hostEdges = topologyManager
+                .getNodesWithNodeConnectorHost();
+        List<Switch> nodes = switchManager.getNetworkDevices();
+        
+        List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
+        for(Switch sw : nodes) {
+               Node n = sw.getNode();
+               SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
+               switchConfigurations.add(config);
+        }
+        
+        // return cache if topology hasn't changed
+        if (
+               (nodeHash != null && hostHash != null && nodeSingleHash != null && nodeConfigurationHash != null) &&
+               nodeHash == nodeEdges.hashCode() && hostHash == hostEdges.hashCode() && nodeSingleHash == nodes.hashCode() && nodeConfigurationHash == switchConfigurations.hashCode()
+        ) {
+               return cache.values();
+        }
+        
+        // cache has changed, we must assign the new values
+        nodeHash = nodeEdges.hashCode();
+        hostHash = hostEdges.hashCode();
+        nodeSingleHash = nodes.hashCode();
+        nodeConfigurationHash = switchConfigurations.hashCode();
+        
+        stage = new HashMap<String, Map<String, Object>>();
+        newNodes = new HashMap<String, Map<String, Object>>();
+
+        // nodeEdges addition
+        addNodes(nodeEdges, topologyManager, switchManager);
+
+        // single nodes addition
+        addSingleNodes(nodes, switchManager);
+        
+        // hostNodes addition
+        addHostNodes(hostEdges, topologyManager);
+        
+        repositionTopology();
+        
+        return cache.values();
+    }
+
+    /**
+     * Add regular nodes to main topology
+     *
+     * @param nodeEdges - node-edges mapping derived from topology
+     * @param topology - the topology instance
+     */
+    private void addNodes(Map<Node, Set<Edge>> nodeEdges,
+            ITopologyManager topology, ISwitchManager switchManager) {
+        Bandwidth bandwidth = new Bandwidth(0);
+        Map<Edge, Set<Property>> properties = topology.getEdges();
+        
+        for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
+            Node n = e.getKey();
+            SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
+            NodeBean node = createNodeBean(config, n);
+            
+            List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
+            Set<Edge> links = e.getValue();
+            for (Edge link : links) {
+                for (Property p : properties.get(link)) {
+                    if (p instanceof Bandwidth) {
+                       bandwidth = (Bandwidth) p;
+                        break;
+                    }
+                }
+                EdgeBean edge = new EdgeBean(link, bandwidth);
+                adjacencies.add(edge.out());
+            }
+            
+            node.setLinks(adjacencies);
+            if (cache.containsKey(node.id())) {
+               Map<String, Object> nodeEntry = cache.get(node.id());
+               if (config != null) {
+                       Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
+                       data.put("$desc", config.getNodeName());
+                       nodeEntry.put("data", data);
+               }
+               stage.put(node.id(), nodeEntry);
+            } else {
+               newNodes.put(node.id(), node.out());
+            }
+        }
+    }
+    
+    protected NodeBean createNodeBean(SwitchConfig config, Node node) {
+       NodeBean bean = null;
+       if (config != null) {
+               bean = new NodeBean(node.toString(), config.getNodeName(), NodeType.NODE);
+       } else {
+               bean = new NodeBean(node.toString(), node.toString(), NodeType.NODE);
+       }
+       
+       return bean;
+    }
+    
+    private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager) {
+       if (nodes == null) return;
+       for (Switch sw : nodes) {
+               Node n = sw.getNode();
+               SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
+               if (cache.containsKey(n.toString()) || newNodes.containsKey(n.toString())) continue;
+               NodeBean node = createNodeBean(config, n);
+               if (cache.containsKey(node.id())) {
+                       Map<String, Object> nodeEntry = cache.get(node.id());
+                       if (config != null) {
+                               Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
+                       data.put("$desc", config.getNodeName());
+                       nodeEntry.put("data", data);
+                       }
+               stage.put(node.id(), nodeEntry);
+            } else {
+               newNodes.put(node.id(), node.out());
+            }
+       }
+    }
+
+    /**
+     * Add regular hosts to main topology
+     *
+     * @param hostEdges - node-nodeconnectors host-specific mapping from topology
+     * @param topology - topology instance
+     */
+    private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
+            ITopologyManager topology) {
+        for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
+            for (NodeConnector connector : e.getValue()) {
+                Host host = topology.getHostAttachedToNodeConnector(connector);
+                EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
+
+                ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
+                addressByteBuffer.putShort((short) 0);
+                addressByteBuffer.put(dmac.getValue());
+                addressByteBuffer.rewind();
+                
+                long hid = addressByteBuffer.getLong();
+                
+                NodeBean hostBean = new NodeBean(String.valueOf(hid), host.getNetworkAddressAsString(), NodeType.HOST);
+                List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
+                EdgeBean edge = new EdgeBean(connector, hid);
+                adjacencies.add(edge.out());
+                hostBean.setLinks(adjacencies);
+                
+                if (cache.containsKey(String.valueOf(hid))) {
+                       stage.put(String.valueOf(hid), cache.get(String.valueOf(hid)));
+                } else {
+                       newNodes.put(String.valueOf(hid), hostBean.out());
+                }
+            }
+        }
+    }
+
+    /**
+     * Re-position nodes in circular layout
+     */
+    private void repositionTopology() {
+        Graph<String, String> graph = new SparseMultigraph<String, String>();
+        cache.clear();
+        cache.putAll(stage);
+        cache.putAll(newNodes);
+        for (Map<String, Object> on : cache.values()) {
+            graph.addVertex(on.toString());
+
+            List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
+            
+            for (Map<String, Object> adj : adjacencies) {
+                graph.addEdge(
+                       adj.toString(), adj.get("nodeFrom").toString(),
+                       adj.get("nodeTo").toString()
+                );
+            }
+        }
+        
+        CircleLayout layout = new CircleLayout(graph);
+        layout.setSize(new Dimension(1200, 365));
+        for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
+            Double x = layout.transform(v.getKey()).getX();
+            Double y = layout.transform(v.getKey()).getY();
+
+            Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
+            nodeData.put("$x", (x - 600) + "");
+            nodeData.put("$y", (y - 225) + "");
+
+            newNodes.get(v.getKey()).put("data", nodeData);
+        }
+    }
+
+    /**
+     * Update node position
+     * 
+     * This method is mainly used by the visual topology
+     *
+     * @param nodeId - The node to update
+     * @return The node object
+     */
+    @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
+    @ResponseBody
+    public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
+               @RequestParam(required = true) String y) {
+       if (!authorize(UserLevel.NETWORKADMIN)) {
+               return new HashMap<String, Object>(); // silently disregard new node position
+       }
+       
+        String id = new String(nodeId);
+        
+        if (!cache.containsKey(id))
+            return null;
+
+        Map<String, Object> node = cache.get(id);
+        Map<String, String> data = (Map<String, String>) node.get("data");
+
+        data.put("$x", x);
+        data.put("$y", y);
+
+        node.put("data", data);
+        
+        return node;
+    }
+    
+    /**
+     * Node object for visual topology
+     */
+    protected class NodeBean {
+       protected String id;
+       protected String name;
+       protected Map<String, String> data;
+       protected List<Map<String, Object>> links;
+       
+       public NodeBean() {
+               data = new HashMap<String, String>();
+               links = new ArrayList<Map<String, Object>>();
+       }
+       
+       public NodeBean(String id, String name, String type) {
+               this();
+               this.id = id;
+               this.name = name;
+               data.put("$desc", name);
+               data.put("$type", type);
+       }
+       
+       public void setLinks(List<Map<String, Object>> links) {
+               this.links = links;
+       }
+       
+       public Map<String, Object> out() {
+               Map<String, Object> node = new HashMap<String, Object>();
+               node.put("id", this.id);
+               node.put("name", this.name);
+               node.put("data", this.data);
+               node.put("adjacencies", this.links);
+               
+               return node;
+       }
+       
+       public String name() {
+               return this.name;
+       }
+       
+       public String id() {
+               return this.id;
+       }
+    }
+    
+    /**
+     * Edge object for visual topology
+     */
+    protected class EdgeBean {
+       protected NodeConnector source;
+       protected NodeConnector destination;
+       protected Map<String, String> data;
+       protected Long hostId;
+       
+       public EdgeBean() {
+               data = new HashMap<String, String>();
+       }
+       
+       public EdgeBean(Edge link, Bandwidth bandwidth) {
+               this();
+               this.source = link.getHeadNodeConnector();
+               this.destination = link.getTailNodeConnector();
+               
+               // data
+               data.put("$bandwidth", bandwidth.toString());
+               data.put("$color", bandwidthColor(bandwidth));
+               data.put("$nodeToPort", destination.getID().toString());
+               data.put("$nodeFromPort", source.getID().toString());
+               data.put("$descFrom", source.getNode().toString());
+               data.put("$descTo", destination.getNode().toString());
+               data.put("$nodeFromPortName", source.toString());
+               data.put("$nodeToPortName", destination.toString());
+       }
+       
+       public EdgeBean(NodeConnector connector, Long hostId) {
+               this();
+               this.source = null;
+               this.destination = connector;
+               this.hostId = hostId;
+               
+               data.put("$bandwidth", "N/A");
+               data.put("$color", bandwidthColor(new Bandwidth(0)));
+               data.put("$nodeToPort", connector.getNodeConnectorIDString());
+               data.put("$nodeFromPort", connector.getNodeConnectorIDString());
+               data.put("$descTo", "");
+               data.put("$descFrom", "");
+               data.put("$nodeToPortName", "");
+               data.put("$nodeFromPortName", "");
+       }
+       
+       public Map<String, Object> out() {
+               Map<String, Object> edge = new HashMap<String, Object>();
+               
+               edge.put("data", data);
+               if (source == null) {
+                       edge.put("nodeFrom", String.valueOf(this.hostId));
+               } else {
+                       edge.put("nodeFrom", source.getNode().toString());
+               }
+               edge.put("nodeTo", destination.getNode().toString());
+               
+               
+               return edge;
+       }
+       
+       private String bandwidthColor(Bandwidth bandwidth) {
+               String color = null;
+               long bandwidthValue = bandwidth.getValue();
+               
+               if (bandwidthValue == 0) {
+                color = "#000";
+            } else if (bandwidthValue < Bandwidth.BW1Kbps) {
+               color = "#148AC6";
+            } else if (bandwidthValue < Bandwidth.BW1Mbps) {
+                color = "#2858A0";
+            } else if (bandwidthValue < Bandwidth.BW1Gbps) {
+                color = "#009393";
+            } else if (bandwidthValue < Bandwidth.BW1Tbps) {
+                color = "#C6C014";
+            } else if (bandwidthValue < Bandwidth.BW1Pbps) {
+                color = "#F9F464";
+            }
+               
+               return color;
+        }
+    }
+    
+    protected class NodeType {
+       public static final String NODE = "swtch";
+       public static final String HOST = "host";
+    }
+    
+    private boolean authorize(UserLevel level) {
+       IUserManager userManager = (IUserManager) ServiceHelper
+                .getGlobalInstance(IUserManager.class, this);
+        if (userManager == null) {
+               return false;
+        }
+        
+        String username = SecurityContextHolder.getContext().getAuthentication().getName();
+        UserLevel userLevel = userManager.getUserLevel(username);
+        if (userLevel.toNumber() <= level.toNumber()) {
+               return true;
+        }
+        return false;
+    }
+}
diff --git a/opendaylight/web/topology/src/main/resources/META-INF/spring.factories b/opendaylight/web/topology/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/web/topology/src/main/resources/META-INF/spring.handlers b/opendaylight/web/topology/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/web/topology/src/main/resources/META-INF/spring.schemas b/opendaylight/web/topology/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/web/topology/src/main/resources/META-INF/spring.tooling b/opendaylight/web/topology/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/web/topology/src/main/resources/WEB-INF/Topology-servlet.xml b/opendaylight/web/topology/src/main/resources/WEB-INF/Topology-servlet.xml
new file mode 100644 (file)
index 0000000..1c05bbc
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans xmlns="http://www.springframework.org/schema/beans"\r
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xmlns:context="http://www.springframework.org/schema/context"\r
+  xmlns:mvc="http://www.springframework.org/schema/mvc"\r
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \r
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd\r
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">\r
+\r
+  <context:component-scan base-package="org.opendaylight.controller.topology.web"/>\r
+  \r
+  <mvc:resources mapping="/js/**" location="/js/" />\r
+  <mvc:resources mapping="/css/**" location="/css/" />\r
+  <mvc:resources mapping="/img/**" location="/img/" />\r
+  <mvc:annotation-driven/>\r
+  \r
+  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">\r
+       <property name="prefix" value="/WEB-INF/jsp/"/>\r
+       <property name="suffix" value=".jsp"/>\r
+  </bean>\r
+</beans>\r
diff --git a/opendaylight/web/topology/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/web/topology/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/web/topology/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/web/topology/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..56302de
--- /dev/null
@@ -0,0 +1,120 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+
+       <http pattern="/css/**" security="none" />
+       <http pattern="/js/**" security="none" />
+       <http pattern="/images/**" security="none" />
+       <http pattern="/favicon.ico" security="none" />
+       <http pattern="/one/css/**" security="none" />
+       <http pattern="/one/js/**" security="none" />
+       <http pattern="/one/images/**" security="none" />
+
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="loginUrlAuthenticationEntryPoint">
+               <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+               <intercept-url pattern="/logout*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+
+
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER, ROLE_APP-USER" />
+               <custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
+               <custom-filter position="LOGOUT_FILTER" ref="logoutFilter" />
+               <custom-filter position="LAST" ref="controllerFilter" />
+               <remember-me services-ref="rememberMeServices" key="SDN" />
+       </http>
+       
+       <beans:bean id="controllerFilter"
+               class="org.opendaylight.controller.web.ControllerCustomFilter" />
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="authenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="authenticationFilter"
+               class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+               <beans:property name="authenticationFailureHandler"
+                       ref="authenticationFailureHandler" />
+               <beans:property name="authenticationSuccessHandler">
+                       <beans:bean
+                               class="org.opendaylight.controller.web.ControllerAuthenticationSuccessHandler">
+                               <beans:property name="targetUrlParameter" value="x-page-url" />
+                               <beans:property name="defaultTargetUrl" value="/" />
+                       </beans:bean>
+               </beans:property>
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.web.ControllerWebSecurityContextRepository" />
+
+       <beans:bean id="authenticationFailureHandler"
+               class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
+               <beans:property name="useForward" value="false" />
+               <beans:property name="defaultFailureUrl" value="/login" />              
+       </beans:bean>
+
+       <beans:bean id="loginUrlAuthenticationEntryPoint"
+               class="org.opendaylight.controller.web.ControllerLoginUrlAuthEntryPoint">
+               <beans:property name="loginFormUrl" value="/login" />
+       </beans:bean>
+
+       <beans:bean id="authenticationProviderWrapper"
+               class="org.opendaylight.controller.web.AuthenticationProviderWrapper" />
+
+    <!-- logout related -->
+    
+    <beans:bean id="logoutHandler"
+        class="org.opendaylight.controller.web.ControllerLogoutHandler" />
+        
+    <beans:bean id="securityContextLogoutHandler"
+        class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />    
+        
+            
+    <beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
+        <!-- if logout succeed then this is the URL -->
+        <beans:constructor-arg value="/login" />
+        <beans:constructor-arg>
+            <beans:list>
+                <beans:ref bean="logoutHandler"/>
+                <beans:ref bean="rememberMeServices"/>
+                <beans:ref bean="securityContextLogoutHandler"/>
+            </beans:list>
+        </beans:constructor-arg>
+        <beans:property name="filterProcessesUrl" value="/logout" />
+    </beans:bean>       
+        
+
+
+
+       <!-- remember me related -->
+       <beans:bean id="rememberMeFilter"
+               class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+       </beans:bean>
+
+       <beans:bean id="rememberMeServices"
+               class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
+               <beans:property name="userDetailsService" ref="userDetailsServiceRef" />
+               <beans:property name="key" value="SDN" />
+               <beans:property name="alwaysRemember" value="true"></beans:property>
+               <beans:property name="tokenValiditySeconds" value="3600" />
+               <beans:property name="cookieName" value="SDN-Controller" />
+       </beans:bean>
+
+       <beans:bean id="userDetailsServiceRef" class="org.opendaylight.controller.web.ControllerUserDetailsService" />
+
+
+       <beans:bean id="rememberMeAuthenticationProvider"
+               class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
+               <beans:property name="key" value="SDN" />
+       </beans:bean>
+       
+</beans:beans>
diff --git a/opendaylight/web/topology/src/main/resources/WEB-INF/web.xml b/opendaylight/web/topology/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..bbf9a34
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+       version="2.4">
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <listener>
+               <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+
+       <servlet>
+               <servlet-name>Topology</servlet-name>
+               <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+               <load-on-startup>1</load-on-startup>
+       </servlet>
+
+       <servlet-mapping>
+               <servlet-name>Topology</servlet-name>
+               <url-pattern>/</url-pattern>
+       </servlet-mapping>
+
+       <filter>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <listener>
+               <listener-class>org.opendaylight.controller.web.ControllerUISessionManager</listener-class>
+       </listener>
+
+</web-app>
diff --git a/opendaylight/web/topology/src/main/resources/js/page.js b/opendaylight/web/topology/src/main/resources/js/page.js
new file mode 100644 (file)
index 0000000..5b4e79f
--- /dev/null
@@ -0,0 +1,113 @@
+
+/* 
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved. 
+ * 
+ * This program and the accompanying materials are made available under the 
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution, 
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+//PAGE topology
+one.f = {};
+
+// specify dashlets and layouts
+one.f.dashlet = {
+    flows : {
+        id : 'dashletFlows',
+        name : 'Flows'
+    },
+    widget : {
+        id : 'dashletWidget',
+        name : 'Acme Widgets'
+    },
+    status : {
+        id : 'dashletStatus',
+        name : 'Network Status'
+    },
+    foo : {
+        id : 'dashletFoo',
+        name : 'Foo'
+    },
+    bar : {
+        id : 'dashletBar',
+        name : 'Bar'
+    }
+};
+
+one.f.menu = {
+    left : {
+        top : [
+            one.f.dashlet.flows,
+            one.f.dashlet.bar
+        ],
+        bottom : [
+            one.f.dashlet.foo
+        ]
+    },
+    right : {
+        top : [],
+        bottom : [
+            one.f.dashlet.widget,
+            one.f.dashlet.status
+        ]
+    }
+};
+
+/** INIT **/
+// populate nav tabs
+$(one.f.menu.left.top).each(function(index, value) {
+    var $nav = $(".nav", "#left-top");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.left.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#left-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.right.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#right-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+one.f.populate = function($dashlet, header) {
+    var $h4 = one.lib.dashlet.header(header);
+    $dashlet.append($h4);
+};
+
+// bind dashlet nav
+$('.dash .nav a', '#main').click(function() {
+    // de/activation
+    var $li = $(this).parent();
+    var $ul = $li.parent();
+    one.lib.nav.unfocus($ul);
+    $li.addClass('active');
+    // clear respective dashlet
+    var $dashlet = $ul.parent().find('.dashlet');
+    one.lib.dashlet.empty($dashlet);
+    // callback based on menu
+    var id = $(this).attr('id');
+    var menu = one.f.dashlet;
+    switch (id) {
+        case menu.foo.id:
+            one.f.populate($dashlet, "Foo");
+            break;
+        case menu.flows.id:
+            one.f.populate($dashlet, "Flows");
+            break;
+        case menu.bar.id:
+            one.f.populate($dashlet, "Bar");
+            break;
+        case menu.widget.id:
+            one.f.populate($dashlet, "Widget");
+            break;
+        case menu.status.id:
+            one.f.populate($dashlet, "Status");
+            break;
+    };
+});
+
+// activate first tab on each dashlet
+$('.dash .nav').each(function(index, value) {
+    $($(value).find('li')[0]).find('a').click();
+});
diff --git a/opendaylight/web/topology/src/test/java/org/opendaylight/controller/topology/web/TopologyTest.java b/opendaylight/web/topology/src/test/java/org/opendaylight/controller/topology/web/TopologyTest.java
new file mode 100644 (file)
index 0000000..16d1754
--- /dev/null
@@ -0,0 +1,33 @@
+package org.opendaylight.controller.topology.web;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.utils.NodeCreator;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.opendaylight.controller.topology.web.Topology.NodeBean;
+               
+public class TopologyTest {
+
+       @Test
+       public void testCreateNodeBean() {
+               Topology topology = new Topology();
+               Node node = NodeCreator.createOFNode(new Long(3));
+               SwitchConfig mockSwitchConfig = new SwitchConfig(node.getNodeIDString(), "foo", null, null);
+               
+               NodeBean bean = topology.createNodeBean(mockSwitchConfig, node);
+               
+               assertNotNull(bean);
+               assertEquals(bean.id, node.toString());
+               assertEquals(bean.name, "foo");
+               
+               bean = topology.createNodeBean(null, node);
+               
+               assertNotNull(bean);
+               assertEquals(bean.id, node.toString());
+               assertEquals(bean.name, bean.id);
+       }
+
+}
diff --git a/opendaylight/web/troubleshoot/pom.xml b/opendaylight/web/troubleshoot/pom.xml
new file mode 100644 (file)
index 0000000..0fa2dda
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.opendaylight.controller</groupId>
+               <artifactId>commons.opendaylight</artifactId>
+               <version>1.4.0-SNAPSHOT</version>
+               <relativePath>../../commons/opendaylight</relativePath>
+       </parent>
+       <groupId>org.opendaylight.controller</groupId>
+       <artifactId>troubleshoot.web</artifactId>
+       <version>0.4.0-SNAPSHOT</version>
+       <packaging>bundle</packaging>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>2.3.6</version>
+                               <extensions>true</extensions>
+                               <configuration>
+                                       <instructions>
+                                               <Export-Package>
+                                               </Export-Package>
+                                               <Import-Package>
+                                                       org.opendaylight.controller.forwardingrulesmanager,
+                                                       org.opendaylight.controller.sal.authorization,
+                                                       org.opendaylight.controller.sal.action,
+                                                       org.opendaylight.controller.sal.core,
+                                                       org.opendaylight.controller.sal.utils,
+                                                       org.opendaylight.controller.containermanager,
+                                                       org.opendaylight.controller.switchmanager,
+                                                       org.opendaylight.controller.statisticsmanager,
+                                                       org.opendaylight.controller.sal.flowprogrammer,
+                                                       org.opendaylight.controller.sal.match,
+                                                       org.opendaylight.controller.sal.reader,
+                                                       org.opendaylight.controller.web,
+                                                       com.google.gson,
+                                                       javax.annotation,
+                                                       javax.naming,
+                                                       javax.servlet,
+                                                       javax.servlet.annotation,
+                                                       javax.servlet.http,
+                                                       javax.servlet.jsp,
+                                                       javax.servlet.jsp.el,
+                                                       javax.servlet.jsp.jstl.core,
+                                                       javax.servlet.jsp.jstl.fmt,
+                                                       javax.servlet.jsp.jstl.tlv,
+                                                       javax.servlet.jsp.tagext,
+                                                       javax.servlet.resources,
+                                                       javax.xml.parsers,
+                                                       javax.xml.transform,
+                                                       org.apache.commons.logging,
+                                                       org.apache.taglibs.standard.functions,
+                                                       org.apache.taglibs.standard.resources,
+                                                       org.apache.taglibs.standard.tag.common.core,
+                                                       org.apache.taglibs.standard.tag.common.fmt,
+                                                       org.apache.taglibs.standard.tag.rt.core,
+                                                       org.apache.taglibs.standard.tag.rt.fmt,
+                                                       org.apache.taglibs.standard.tei,
+                                                       org.apache.taglibs.standard.tlv,
+                                                       org.codehaus.jackson,
+                                                       org.codehaus.jackson.annotate,
+                                                       org.codehaus.jackson.map,
+                                                       org.codehaus.jackson.map.annotate,
+                                                       org.osgi.framework,
+                                                       org.slf4j,
+                                                       org.springframework.beans,
+                                                       org.springframework.beans.factory.xml,
+                                                       org.springframework.context.config,
+                                                       org.springframework.stereotype,
+                                                       org.springframework.web,
+                                                       org.springframework.web.bind.annotation,
+                                                       org.springframework.web.servlet,
+                                                       org.springframework.web.servlet.config,
+                                                       org.springframework.web.servlet.view,
+
+                                                       org.springframework.web.filter,
+                                                       org.springframework.web.context,
+                                                       org.springframework.security.core,
+                                                       org.springframework.security.core.userdetails,
+                                                       org.springframework.security.core.authority,
+                                                       org.springframework.security.core.context,
+                                                       org.springframework.security.authentication,
+                                                       org.springframework.security.config,
+                                                       org.springframework.security.config.authentication,
+                                                       org.springframework.security.taglibs.authz,
+                                                       org.springframework.security.web,
+                                                       org.springframework.security.web.context,
+                                                       org.springframework.security.web.authentication,
+                                                       org.springframework.security.web.authentication.www,
+                                                       org.springframework.security.provisioning,
+                                                       org.springframework.security.web.util,
+                                                       org.springframework.security.web.authentication.rememberme,
+                                                       org.springframework.security.web.authentication.logout,
+                                                       org.springframework.dao
+                                               </Import-Package>
+                                               <Web-ContextPath>/one/troubleshoot</Web-ContextPath>
+                                       </instructions>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+       <dependencies>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>forwardingrulesmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>containermanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>switchmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>sal</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>statisticsmanager</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.opendaylight.controller</groupId>
+                       <artifactId>web</artifactId>
+                       <version>0.4.0-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java b/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/Troubleshoot.java
new file mode 100644 (file)
index 0000000..e20c35a
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.troubleshoot.web;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.action.SetVlanId;
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.TimeStamp;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+import org.opendaylight.controller.sal.reader.NodeConnectorStatistics;
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.GlobalConstants;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.IPProtocols;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.statisticsmanager.IStatisticsManager;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.opendaylight.controller.web.IOneWeb;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping("/")
+public class Troubleshoot implements IOneWeb {
+    private static final UserLevel AUTH_LEVEL = UserLevel.CONTAINERUSER;
+    private final String WEB_NAME = "Troubleshoot";
+    private final String WEB_ID = "troubleshoot";
+    private final short WEB_ORDER = 4;
+    private final String containerName = GlobalConstants.DEFAULT.toString();
+
+    public Troubleshoot() {
+        ServiceHelper.registerGlobalService(IOneWeb.class, this, null);
+    }
+
+    @Override
+    public String getWebName() {
+        return WEB_NAME;
+    }
+
+    @Override
+    public String getWebId() {
+        return WEB_ID;
+    }
+
+    @Override
+    public short getWebOrder() {
+        return WEB_ORDER;
+    }
+
+    @Override
+    public boolean isAuthorized(UserLevel userLevel) {
+        return userLevel.ordinal() <= AUTH_LEVEL.ordinal();
+    }
+
+    @RequestMapping(value = "/existingNodes", method = RequestMethod.GET)
+    @ResponseBody
+    public TroubleshootingJsonBean getExistingNodes() {
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        List<HashMap<String, String>> lines = new ArrayList<HashMap<String, String>>();
+        Set<Node> nodeSet = null;
+        if (switchManager != null) {
+            nodeSet = switchManager.getNodes();
+        } else {
+            // TODO: Change to use logger instead.
+            System.out.println("SwitchManager reference is NULL");
+        }
+        if (nodeSet != null) {
+            for (Node node : nodeSet) {
+                HashMap<String, String> device = new HashMap<String, String>();
+                SwitchConfig switchConfig = switchManager.getSwitchConfig(node.getNodeIDString());
+                device.put("nodeName", switchConfig == null ? "" : switchConfig.getNodeName());
+                device.put("nodeId", node.toString());
+                lines.add(device);
+            }
+        }
+        TroubleshootingJsonBean result = new TroubleshootingJsonBean();
+
+        List<String> guiFieldNames = new ArrayList<String>();
+        guiFieldNames.add("Node Names");
+        guiFieldNames.add("Node ID");
+        guiFieldNames.add("Statistics");
+
+        result.setColumnNames(guiFieldNames);
+        result.setNodeData(lines);
+        return result;
+    }
+
+    @RequestMapping(value = "/uptime", method = RequestMethod.GET)
+    @ResponseBody
+    public TroubleshootingJsonBean getUptime() {
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        List<HashMap<String, String>> lines = new ArrayList<HashMap<String, String>>();
+        Set<Node> nodeSet = null;
+        if (switchManager != null) {
+            nodeSet = switchManager.getNodes();
+        } else {
+            // TODO: Change to use logger instead.
+            System.out.println("SwitchManager reference is NULL");
+        }
+        if (nodeSet != null) {
+            for (Node node : nodeSet) {
+                HashMap<String, String> device = new HashMap<String, String>();
+                SwitchConfig switchConfig = switchManager.getSwitchConfig(node.getNodeIDString());
+                device.put("nodeName", switchConfig == null ? "" : switchConfig.getNodeName());
+                device.put("nodeId", node.toString());
+                TimeStamp timeStamp = (TimeStamp) switchManager.getNodeProp(
+                        node, TimeStamp.TimeStampPropName);
+                Long time = (timeStamp == null) ? 0 : timeStamp.getValue();
+                String date = (time == 0) ? "" : (new Date(time)).toString();
+                device.put("connectedSince", date);
+                lines.add(device);
+            }
+        }
+        TroubleshootingJsonBean result = new TroubleshootingJsonBean();
+
+        List<String> guiFieldNames = new ArrayList<String>();
+        guiFieldNames.add("Node Names");
+        guiFieldNames.add("Node ID");
+        guiFieldNames.add("Connected");
+
+        result.setColumnNames(guiFieldNames);
+        result.setNodeData(lines);
+        return result;
+    }
+
+    @RequestMapping(value = "/flowStats", method = RequestMethod.GET)
+    @ResponseBody
+    public TroubleshootingJsonBean getFlowStats(
+            @RequestParam("nodeId") String nodeId) {
+        Node node = Node.fromString(nodeId);
+        List<HashMap<String, String>> cells = new ArrayList<HashMap<String, String>>();
+        IStatisticsManager statisticsManager = (IStatisticsManager) ServiceHelper
+                .getInstance(IStatisticsManager.class, containerName, this);
+
+        List<FlowOnNode> statistics = statisticsManager.getFlows(node);
+        for (FlowOnNode stats : statistics) {
+            cells.add(this.convertFlowStatistics(node, stats));
+        }
+        List<String> columnNames = new ArrayList<String>();
+        columnNames.addAll(Arrays.asList(new String[] { "Node", "In Port",
+                "DL Src", "DL Dst", "DL Type", "DL Vlan", "NW Src", "NW Dst",
+                "NW Proto", "TP Src", "TP Dst", "Actions", "Bytes", "Packets",
+                "Time (s)", "Timeout (s)", "Out Port(s)", "Out Vlan",
+                "Priority" }));
+        TroubleshootingJsonBean result = new TroubleshootingJsonBean();
+        result.setColumnNames(columnNames);
+        result.setNodeData(cells);
+        return result;
+    }
+
+    @RequestMapping(value = "/portStats", method = RequestMethod.GET)
+    @ResponseBody
+    public TroubleshootingJsonBean getPortStats(
+            @RequestParam("nodeId") String nodeId) {
+        Node node = Node.fromString(nodeId);
+        List<HashMap<String, String>> cells = new ArrayList<HashMap<String, String>>();
+        IStatisticsManager statisticsManager = (IStatisticsManager) ServiceHelper
+                .getInstance(IStatisticsManager.class, containerName, this);
+        List<NodeConnectorStatistics> statistics = statisticsManager
+                .getNodeConnectorStatistics(node);
+        for (NodeConnectorStatistics stats : statistics) {
+            cells.add(this.convertPortsStatistics(stats));
+        }
+        TroubleshootingJsonBean result = new TroubleshootingJsonBean();
+        List<String> columnNames = new ArrayList<String>();
+        columnNames.addAll(Arrays.asList(new String[] { "Node Connector",
+                "Rx Pkts", "Tx Pkts", "Rx Bytes", "Tx Bytes", "Rx Drops",
+                "Tx Drops", "Rx Errs", "Tx Errs", "Rx Frame Errs",
+                "Rx OverRun Errs", "Rx CRC Errs", "Collisions" }));
+        result.setColumnNames(columnNames);
+        result.setNodeData(cells);
+        return result;
+    }
+
+    private HashMap<String, String> convertPortsStatistics(
+            NodeConnectorStatistics ncStats) {
+        HashMap<String, String> row = new HashMap<String, String>();
+
+        row.put("nodeConnector",
+                String.valueOf(ncStats.getNodeConnector().toString()));
+        row.put("rxPkts", String.valueOf(ncStats.getReceivePacketCount()));
+        row.put("txPkts", String.valueOf(ncStats.getTransmitPacketCount()));
+        row.put("rxBytes", String.valueOf(ncStats.getReceiveByteCount()));
+        row.put("txBytes", String.valueOf(ncStats.getTransmitByteCount()));
+        row.put("rxDrops", String.valueOf(ncStats.getReceiveDropCount()));
+        row.put("txDrops", String.valueOf(ncStats.getTransmitDropCount()));
+        row.put("rxErrors", String.valueOf(ncStats.getReceiveErrorCount()));
+        row.put("txErrors", String.valueOf(ncStats.getTransmitErrorCount()));
+        row.put("rxFrameErrors",
+                String.valueOf(ncStats.getReceiveFrameErrorCount()));
+        row.put("rxOverRunErrors",
+                String.valueOf(ncStats.getReceiveOverRunErrorCount()));
+        row.put("rxCRCErrors",
+                String.valueOf(ncStats.getReceiveCRCErrorCount()));
+        row.put("collisions", String.valueOf(ncStats.getCollisionCount()));
+
+        return row;
+    }
+
+    private HashMap<String, String> convertFlowStatistics(Node node,
+            FlowOnNode flowOnNode) {
+        HashMap<String, String> row = new HashMap<String, String>();
+        Flow flow = flowOnNode.getFlow();
+        Match match = flow.getMatch();
+        row.put("nodeName", getNodeName(node));
+        if (match.isPresent(MatchType.IN_PORT)) {
+            row.put(MatchType.IN_PORT.id(), ((NodeConnector) flow.getMatch()
+                    .getField(MatchType.IN_PORT).getValue()).getID().toString());
+        } else {
+            row.put(MatchType.IN_PORT.id(), "*");
+        }
+        if (match.isPresent(MatchType.DL_SRC)) {
+            row.put(MatchType.DL_SRC.id(),
+                    (HexEncode.bytesToHexString(((byte[]) flow.getMatch()
+                            .getField(MatchType.DL_SRC).getValue()))));
+        } else {
+            row.put(MatchType.DL_SRC.id(), "*");
+        }
+        if (match.isPresent(MatchType.DL_DST)) {
+            row.put(MatchType.DL_DST.id(),
+                    (HexEncode.bytesToHexString(((byte[]) flow.getMatch()
+                            .getField(MatchType.DL_DST).getValue()))));
+        } else {
+            row.put(MatchType.DL_DST.id(), "*");
+        }
+        if (match.isPresent(MatchType.DL_TYPE)) {
+            row.put(MatchType.DL_TYPE.id(),
+                    EtherTypes.getEtherTypeName(((Short) flow.getMatch()
+                            .getField(MatchType.DL_TYPE).getValue())));
+        } else {
+            row.put(MatchType.DL_TYPE.id(), "*");
+        }
+
+        // Some physical switch has vlan as ffff to show "any" vlan
+        if (match.isPresent(MatchType.DL_VLAN)) {
+            if (((Short) flow.getMatch().getField(MatchType.DL_VLAN).getValue())
+                    .shortValue() < 0) {
+                row.put(MatchType.DL_VLAN.id(), "0");
+            } else {
+                row.put(MatchType.DL_VLAN.id(), ((Short) flow.getMatch()
+                        .getField(MatchType.DL_VLAN).getValue()).toString());
+            }
+        } else {
+            row.put(MatchType.DL_VLAN.id(), "*");
+        }
+        if (match.isPresent(MatchType.NW_SRC)) {
+            row.put(MatchType.NW_SRC.id(), ((InetAddress) flow.getMatch()
+                    .getField(MatchType.NW_SRC).getValue()).getHostAddress());
+        } else {
+            row.put(MatchType.NW_SRC.id(), "*");
+        }
+        if (match.isPresent(MatchType.NW_DST)) {
+            row.put(MatchType.NW_DST.id(), ((InetAddress) flow.getMatch()
+                    .getField(MatchType.NW_DST).getValue()).getHostAddress());
+        } else {
+            row.put(MatchType.NW_DST.id(), "*");
+        }
+        if (match.isPresent(MatchType.NW_PROTO)) {
+            row.put(MatchType.NW_PROTO.id(),
+                    IPProtocols.getProtocolName(((Byte) flow.getMatch()
+                            .getField(MatchType.NW_PROTO).getValue())));
+        } else {
+            row.put(MatchType.NW_PROTO.id(), "*");
+        }
+        if (match.isPresent(MatchType.TP_SRC)) {
+            Short tpSrc = (Short) (flow.getMatch().getField(MatchType.TP_SRC)
+                    .getValue());
+            if (tpSrc < 0) {
+                row.put(MatchType.TP_SRC.id(),
+                        ((Integer) (tpSrc.intValue() & 0x7FFF | 0x8000))
+                                .toString());
+            } else {
+                row.put(MatchType.TP_SRC.id(), tpSrc.toString());
+            }
+        } else {
+            row.put(MatchType.TP_SRC.id(), "*");
+        }
+        if (match.isPresent(MatchType.TP_DST)) {
+            Short tpDst = (Short) (flow.getMatch().getField(MatchType.TP_DST)
+                    .getValue());
+            if (tpDst < 0) {
+                row.put(MatchType.TP_DST.id(),
+                        ((Integer) (tpDst.intValue() & 0x7FFF | 0x8000))
+                                .toString());
+            } else {
+                row.put(MatchType.TP_DST.id(), tpDst.toString());
+            }
+        } else {
+            row.put(MatchType.TP_DST.id(), "*");
+        }
+
+        row.put("byteCount", ((Long) flowOnNode.getByteCount()).toString());
+        row.put("packetCount", ((Long) flowOnNode.getPacketCount()).toString());
+
+        StringBuffer actions = new StringBuffer();
+        StringBuffer outPorts = new StringBuffer();
+        String outVlanId = null;
+        for (Action action : flow.getActions()) {
+            actions.append(action.getType().toString() + "\n");
+            if (action instanceof Output) {
+                Output ao = (Output) action;
+                if (outPorts.length() > 0) {
+                    outPorts.append(" ");
+                }
+                outPorts.append(ao.getPort().getNodeConnectorIdAsString());
+            } else if (action instanceof SetVlanId) {
+                SetVlanId av = (SetVlanId) action;
+                outVlanId = String.valueOf(av.getVlanId());
+            }
+        }
+        if (outPorts.length() == 0) {
+            outPorts.append("*");
+        }
+        if (outVlanId == null) {
+            outVlanId = "*";
+        }
+        row.put("actions", actions.toString());
+        row.put("outPorts", outPorts.toString());
+        row.put("outVlanId", outVlanId);
+        row.put("durationSeconds",
+                ((Integer) flowOnNode.getDurationSeconds()).toString());
+        row.put("idleTimeout", ((Short) flow.getIdleTimeout()).toString());
+        row.put("priority", String.valueOf(flow.getPriority()));
+        return row;
+    }
+
+    private String getNodeName(Node node) {
+        String nodeName = "";
+        ISwitchManager switchManager = (ISwitchManager) ServiceHelper
+                .getInstance(ISwitchManager.class, containerName, this);
+        if (switchManager != null) {
+            SwitchConfig config = switchManager.getSwitchConfig(node
+                    .getNodeIDString());
+            if (config != null) {
+                nodeName = config.getNodeName();
+            }
+        }
+        return nodeName;
+    }
+}
diff --git a/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/TroubleshootingJsonBean.java b/opendaylight/web/troubleshoot/src/main/java/org/opendaylight/controller/troubleshoot/web/TroubleshootingJsonBean.java
new file mode 100644 (file)
index 0000000..1030c49
--- /dev/null
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.troubleshoot.web;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class TroubleshootingJsonBean {
+    private List<String> columnNames;
+    private List<HashMap<String, String>> nodeData;
+
+    public List<String> getColumnNames() {
+        return columnNames;
+    }
+
+    public void setColumnNames(List<String> columnNames) {
+        this.columnNames = columnNames;
+    }
+
+    public List<HashMap<String, String>> getNodeData() {
+        return nodeData;
+    }
+
+    public void setNodeData(List<HashMap<String, String>> nodeData) {
+        this.nodeData = nodeData;
+    }
+}
diff --git a/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.factories b/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.handlers b/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.schemas b/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.tooling b/opendaylight/web/troubleshoot/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/Troubleshoot-servlet.xml b/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/Troubleshoot-servlet.xml
new file mode 100644 (file)
index 0000000..877617a
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans xmlns="http://www.springframework.org/schema/beans"\r
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xmlns:context="http://www.springframework.org/schema/context"\r
+  xmlns:mvc="http://www.springframework.org/schema/mvc"\r
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \r
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd\r
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">\r
+\r
+  <context:component-scan base-package="org.opendaylight.controller.troubleshoot.web"/>\r
+  \r
+  <mvc:resources mapping="/js/**" location="/js/" />\r
+  <mvc:resources mapping="/css/**" location="/css/" />\r
+  <mvc:resources mapping="/img/**" location="/img/" />\r
+  <mvc:annotation-driven/>\r
+  \r
+  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">\r
+       <property name="prefix" value="/WEB-INF/jsp/"/>\r
+       <property name="suffix" value=".jsp"/>\r
+  </bean>\r
+</beans>\r
diff --git a/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/spring/context.xml b/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/spring/context.xml
new file mode 100644 (file)
index 0000000..8a4bda5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+        <import resource="servlet/security.xml"/>
+
+</beans>
diff --git a/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/spring/servlet/security.xml b/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/spring/servlet/security.xml
new file mode 100644 (file)
index 0000000..641042c
--- /dev/null
@@ -0,0 +1,120 @@
+<beans:beans xmlns="http://www.springframework.org/schema/security"
+       xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/security
+           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+
+       <http pattern="/css/**" security="none" />
+       <http pattern="/js/**" security="none" />
+       <http pattern="/images/**" security="none" />
+       <http pattern="/favicon.ico" security="none" />
+       <http pattern="/one/css/**" security="none" />
+       <http pattern="/one/js/**" security="none" />
+       <http pattern="/one/images/**" security="none" />
+
+
+       <http auto-config="false" authentication-manager-ref="authenticationManager"
+               security-context-repository-ref="securityContextRepo" entry-point-ref="loginUrlAuthenticationEntryPoint">
+               <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+               <intercept-url pattern="/logout*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
+
+
+               <intercept-url pattern="/**"
+                       access="ROLE_SYSTEM-ADMIN, ROLE_NETWORK-ADMIN, ROLE_NETWORK-OPERATOR, ROLE_CONTAINER-USER" />
+               <custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
+               <custom-filter position="LOGOUT_FILTER" ref="logoutFilter" />
+               <custom-filter position="LAST" ref="controllerFilter" />
+               <remember-me services-ref="rememberMeServices" key="SDN" />
+       </http>
+       
+       <beans:bean id="controllerFilter"
+               class="org.opendaylight.controller.web.ControllerCustomFilter" />
+
+       <authentication-manager id="authenticationManager">
+               <authentication-provider ref="authenticationProviderWrapper" />
+       </authentication-manager>
+
+       <beans:bean id="authenticationFilter"
+               class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+               <beans:property name="authenticationFailureHandler"
+                       ref="authenticationFailureHandler" />
+               <beans:property name="authenticationSuccessHandler">
+                       <beans:bean
+                               class="org.opendaylight.controller.web.ControllerAuthenticationSuccessHandler">
+                               <beans:property name="targetUrlParameter" value="x-page-url" />
+                               <beans:property name="defaultTargetUrl" value="/" />
+                       </beans:bean>
+               </beans:property>
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+       </beans:bean>
+
+       <beans:bean id="securityContextRepo"
+               class="org.opendaylight.controller.web.ControllerWebSecurityContextRepository" />
+
+       <beans:bean id="authenticationFailureHandler"
+               class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
+               <beans:property name="useForward" value="false" />
+               <beans:property name="defaultFailureUrl" value="/login" />              
+       </beans:bean>
+
+       <beans:bean id="loginUrlAuthenticationEntryPoint"
+               class="org.opendaylight.controller.web.ControllerLoginUrlAuthEntryPoint">
+               <beans:property name="loginFormUrl" value="/login" />
+       </beans:bean>
+
+       <beans:bean id="authenticationProviderWrapper"
+               class="org.opendaylight.controller.web.AuthenticationProviderWrapper" />
+
+    <!-- logout related -->
+    
+    <beans:bean id="logoutHandler"
+        class="org.opendaylight.controller.web.ControllerLogoutHandler" />
+        
+    <beans:bean id="securityContextLogoutHandler"
+        class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />    
+        
+            
+    <beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
+        <!-- if logout succeed then this is the URL -->
+        <beans:constructor-arg value="/login" />
+        <beans:constructor-arg>
+            <beans:list>
+                <beans:ref bean="logoutHandler"/>
+                <beans:ref bean="rememberMeServices"/>
+                <beans:ref bean="securityContextLogoutHandler"/>
+            </beans:list>
+        </beans:constructor-arg>
+        <beans:property name="filterProcessesUrl" value="/logout" />
+    </beans:bean>       
+        
+
+
+
+       <!-- remember me related -->
+       <beans:bean id="rememberMeFilter"
+               class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
+               <beans:property name="rememberMeServices" ref="rememberMeServices" />
+               <beans:property name="authenticationManager" ref="authenticationManager" />
+       </beans:bean>
+
+       <beans:bean id="rememberMeServices"
+               class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
+               <beans:property name="userDetailsService" ref="userDetailsServiceRef" />
+               <beans:property name="key" value="SDN" />
+               <beans:property name="alwaysRemember" value="true"></beans:property>
+               <beans:property name="tokenValiditySeconds" value="3600" />
+               <beans:property name="cookieName" value="SDN-Controller" />
+       </beans:bean>
+
+       <beans:bean id="userDetailsServiceRef" class="org.opendaylight.controller.web.ControllerUserDetailsService" />
+
+
+       <beans:bean id="rememberMeAuthenticationProvider"
+               class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
+               <beans:property name="key" value="SDN" />
+       </beans:bean>
+       
+</beans:beans>
diff --git a/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/web.xml b/opendaylight/web/troubleshoot/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..80d0041
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+       version="2.4">
+
+       <context-param>
+               <param-name>contextConfigLocation</param-name>
+               <param-value>/WEB-INF/spring/*.xml</param-value>
+       </context-param>
+
+       <listener>
+               <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+       </listener>
+
+
+       <servlet>
+               <servlet-name>Troubleshoot</servlet-name>
+               <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+               <load-on-startup>1</load-on-startup>
+       </servlet>
+
+       <servlet-mapping>
+               <servlet-name>Troubleshoot</servlet-name>
+               <url-pattern>/</url-pattern>
+       </servlet-mapping>
+
+
+       <filter>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>springSecurityFilterChain</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <listener>
+               <listener-class>org.opendaylight.controller.web.ControllerUISessionManager</listener-class>
+       </listener>
+</web-app>
diff --git a/opendaylight/web/troubleshoot/src/main/resources/js/page.js b/opendaylight/web/troubleshoot/src/main/resources/js/page.js
new file mode 100644 (file)
index 0000000..dba8477
--- /dev/null
@@ -0,0 +1,305 @@
+
+/* 
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved. 
+ * 
+ * This program and the accompanying materials are made available under the 
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution, 
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ */
+
+//PAGE troubleshoot
+one.f = {};
+
+// specify dashlets and layouts
+one.f.dashlet = {
+    existingNodes : {
+        id : 'existingNodes',
+        name : 'Existing Nodes'
+    },
+    uptime: {
+       id: 'uptime',
+       name: 'Uptime'
+    },
+    flowsOrPorts: {
+       id: "flowsOrPorts",
+       name: "Statistics"
+    }
+};
+
+one.f.menu = {
+    left : {
+        top : [
+            one.f.dashlet.existingNodes
+        ],
+        bottom : [
+            one.f.dashlet.uptime
+        ]
+    },
+    right : {
+        top : [],
+        bottom : [
+            one.f.dashlet.flowsOrPorts
+        ]
+    }
+};
+
+/** INIT **/
+// populate nav tabs
+$(one.f.menu.left.top).each(function(index, value) {
+    var $nav = $(".nav", "#left-top");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.left.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#left-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+$(one.f.menu.right.bottom).each(function(index, value) {
+    var $nav = $(".nav", "#right-bottom");
+    one.main.page.dashlet($nav, value);
+});
+
+/**Troubleshoot modules*/
+one.f.troubleshooting = {
+       rootUrl: "/one/troubleshoot",
+       rightBottomDashlet: { 
+               get: function() {
+                       var $rightBottomDashlet = $("#right-bottom").find(".dashlet");
+                       return $rightBottomDashlet;
+               },
+               setDashletHeader: function(label) {
+                       $("#right-bottom li a")[0].innerHTML = label; 
+               }
+       },
+       createTable: function(columnNames, body) {
+               var tableAttributes = ["table-striped", "table-bordered", "table-condensed"];
+               var $table = one.lib.dashlet.table.table(tableAttributes);
+               var tableHeaders = columnNames;
+               var $thead = one.lib.dashlet.table.header(tableHeaders);
+               var $tbody = one.lib.dashlet.table.body(body, tableHeaders);
+               $table.append($thead)
+                       .append($tbody);
+               return $table;
+       }
+};
+
+one.f.troubleshooting.existingNodes = {
+               id: {
+                       popout: "one_f_troubleshooting_existingNodes_id_popout",
+                       modal: "one_f_troubleshooting_existingNodes_id_modal"
+               },
+               load: {
+                       main: function($dashlet) {
+                               one.lib.dashlet.empty($dashlet);
+                               $dashlet.append(one.lib.dashlet.header(one.f.dashlet.existingNodes.name));
+                               
+                               // TODO(l): Add a generic auto expand function to one.lib and replace custom height setting.
+                               //$('#left-top').height('100%');
+                               one.f.troubleshooting.existingNodes.ajax(one.f.troubleshooting.rootUrl + "/existingNodes" , function(content) {
+                                       var body = one.f.troubleshooting.existingNodes.data.existingNodes(content);
+                                       var $table = one.f.troubleshooting.createTable(content.columnNames, body);
+                                       $dashlet.append($table);
+                               });
+                       },
+                       flows: function(nodeId) {
+                               try {
+                                       clearTimeout(one.f.troubleshooting.existingNodes.registry.refreshTimer);
+                                       $.getJSON(one.main.constants.address.prefix + "/troubleshoot/flowStats?nodeId=" + nodeId, function(content) {
+                                               var body = one.f.troubleshooting.existingNodes.data.flows(content);
+                                               var $table = one.f.troubleshooting.createTable(content.columnNames, body);
+                                               $rightBottomDashlet = one.f.troubleshooting.rightBottomDashlet.get();
+                                               one.f.troubleshooting.rightBottomDashlet.setDashletHeader("Flows");
+                                               one.lib.dashlet.empty($rightBottomDashlet);
+                                               $rightBottomDashlet.append(one.lib.dashlet.header("Flow Details"));
+                                               $rightBottomDashlet.append($table);
+                                               one.f.troubleshooting.existingNodes.registry.refreshTimer = setTimeout(
+                                                               one.f.troubleshooting.existingNodes.load.flows, 5000, nodeId);
+                                       });
+                               } catch(e) {}
+                       },
+                       ports: function(nodeId) {
+                               try {
+                                       clearTimeout(one.f.troubleshooting.existingNodes.registry.refreshTimer);
+                                       $.getJSON(one.main.constants.address.prefix + "/troubleshoot/portStats?nodeId=" + nodeId, function(content) {
+                                               var body = one.f.troubleshooting.existingNodes.data.ports(content);
+                                               var $table = one.f.troubleshooting.createTable(content.columnNames, body);
+                                               $rightBottomDashlet = one.f.troubleshooting.rightBottomDashlet.get();
+                                               one.f.troubleshooting.rightBottomDashlet.setDashletHeader("Ports");
+                                               one.lib.dashlet.empty($rightBottomDashlet);
+                                               $rightBottomDashlet.append(one.lib.dashlet.header("Port Details"));
+                                               $rightBottomDashlet.append($table);
+                                               one.f.troubleshooting.existingNodes.registry.refreshTimer = setTimeout(
+                                                               one.f.troubleshooting.existingNodes.load.ports, 5000, nodeId);
+                                       });
+                               } catch(e) {}
+                       } 
+               },
+               ajax : function(url, callback) {
+                       $.getJSON(url, function(data) {
+                               callback(data);
+                       });
+               },
+               registry: {},
+               modal : {
+               },
+               data : {
+                       existingNodes : function(data) {
+                               var result = [];
+                               $.each(data.nodeData, function(key, value) {
+                                       var tr = {};
+                                       var entry = [];
+                                       entry.push(value["nodeName"]);
+                                       entry.push(value["nodeId"]);
+                                       var nodeIdvalue = value["nodeId"];
+                                       entry.push("<a href=\"javascript:one.f.troubleshooting.existingNodes.load.flows('" + value["nodeId"] + "');\">Flows</a>" + 
+                                                       " <a href=\"javascript:one.f.troubleshooting.existingNodes.load.ports('" + value["nodeId"] + "');\">Ports</a>");
+                                       tr.entry = entry;
+                                       result.push(tr);
+                               });
+                               return result;
+                       },
+                       ports: function(data) {
+                               var result = [];
+                               $.each(data.nodeData, function(key, value) {
+                                       var tr = {};
+                                       var entry = [];
+                                       entry.push(value["nodeConnector"]);
+                                       entry.push(value["rxPkts"]);
+                                       entry.push(value["txPkts"]);
+                                       entry.push(value["rxBytes"]);
+                                       entry.push(value["txBytes"]);
+                                       entry.push(value["rxDrops"]);
+                                       entry.push(value["txDrops"]);
+                                       entry.push(value["rxErrors"]);
+                                       entry.push(value["txErrors"]);
+                                       entry.push(value["rxFrameErrors"]);
+                                       entry.push(value["rxOverRunErrors"]);
+                                       entry.push(value["rxCRCErrors"]);
+                                       entry.push(value["collisions"]);
+                                       tr.entry = entry;
+                                       result.push(tr);
+                               });
+                               return result;
+                       },
+                       flows: function(data) {
+                               var result = [];
+                               $.each(data.nodeData, function(key, value) {
+                                       var tr = {};
+                                       var entry = [];
+                                       entry.push(value["nodeName"]);
+                                       entry.push(value["inPort"]);
+                                       entry.push(value["dlSrc"]);
+                                       entry.push(value["dlDst"]);
+                                       entry.push(value["dlType"]);
+                                       entry.push(value["dlVlan"]);
+                                       entry.push(value["nwSrc"]);
+                                       entry.push(value["nwDst"]);
+                                       entry.push(value["nwProto"]);
+                                       entry.push(value["tpSrc"]);
+                                       entry.push(value["tpDst"]);
+                                       entry.push(value["actions"]);
+                                       entry.push(value["byteCount"]);
+                                       entry.push(value["packetCount"]);
+                                       entry.push(value["durationSeconds"]);
+                                       entry.push(value["idleTimeout"]);
+                                       entry.push(value["outPorts"]);
+                                       entry.push(value["outVlanId"]);
+                                       entry.push(value["priority"]);
+                                       tr.entry = entry;
+                                       result.push(tr);
+                               });
+                               return result;
+                       }
+               }
+};
+
+one.f.troubleshooting.uptime = {
+       id: {
+               popout: "one_f_troubleshooting_existingNodes_id_popout",
+               modal: "one_f_troubleshooting_existingNodes_id_modal"
+       },
+
+       dashlet: function($dashlet) {
+                       one.lib.dashlet.empty($dashlet);
+                       $dashlet.append(one.lib.dashlet.header(one.f.dashlet.uptime.name));
+                       var url = one.f.troubleshooting.rootUrl + "/uptime";
+                       one.f.troubleshooting.uptime.ajax.main(url , {} ,function(content) {
+                               var body = one.f.troubleshooting.uptime.data.uptime(content);
+                               var $table = one.f.troubleshooting.createTable(content.columnNames, body);
+                               $dashlet.append($table);
+                       });
+       },
+       
+       ajax : {
+               main : function(url, requestData, callback) {
+                       $.getJSON(url, requestData, function(data) {
+                               callback(data);
+                       });
+               }
+       },
+       
+       data: {
+               uptime: function(data) {
+                       var result = [];
+                       $.each(data.nodeData, function(key, value) {
+                               var tr = {};
+                               var entry = [];
+                               entry.push(value["nodeName"]);
+                               entry.push(value["nodeId"]);
+                               entry.push(value["connectedSince"]);
+                               tr.entry = entry;
+                               result.push(tr);
+                       });
+                       return result;
+               }
+       },
+};
+
+one.f.troubleshooting.statistics = {
+       dashlet : function($dashlet) {
+        var $h4 = one.lib.dashlet.header("Statistics");
+        $dashlet.append($h4);
+               // empty
+               var $none = $(document.createElement('div'));
+               $none.addClass('none');
+               var $p = $(document.createElement('p'));
+               $p.text('Please select a Flow or Ports statistics');
+               $p.addClass('text-center').addClass('text-info');
+               
+               $dashlet.append($none)
+                       .append($p);
+       }
+};
+
+// bind dashlet nav
+$('.dash .nav a', '#main').click(function() {
+    // de/activation
+    var $li = $(this).parent();
+    var $ul = $li.parent();
+    one.lib.nav.unfocus($ul);
+    $li.addClass('active');
+    // clear respective dashlet
+    var $dashlet = $ul.parent().find('.dashlet');
+    one.lib.dashlet.empty($dashlet);
+    // callback based on menu
+    var id = $(this).attr('id');
+    var menu = one.f.dashlet;
+    switch (id) {
+        case menu.existingNodes.id:
+               one.f.troubleshooting.existingNodes.load.main($dashlet);
+            break;
+        case menu.uptime.id:
+               one.f.troubleshooting.uptime.dashlet($dashlet);
+                       break;
+               case menu.flowsOrPorts.id:
+                       one.f.troubleshooting.statistics.dashlet($dashlet);
+                       break;
+    };
+});
+
+// activate first tab on each dashlet
+$('.dash .nav').each(function(index, value) {
+    $($(value).find('li')[0]).find('a').click();
+});