You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/01/21 14:05:10 UTC

[58/64] incubator-nifi git commit: Merge branch 'develop' into NIFI-250

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c5d452c1/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
----------------------------------------------------------------------
diff --cc nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 0000000,9a2dc30..027bae0
mode 000000,100644..100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@@ -1,0 -1,2142 +1,2268 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.nifi.web.api.dto;
+ 
+ import java.text.Collator;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.Comparator;
+ import java.util.Date;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.LinkedHashMap;
+ import java.util.LinkedHashSet;
+ import java.util.List;
+ import java.util.Locale;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.TreeMap;
+ import java.util.TreeSet;
+ import java.util.concurrent.TimeUnit;
+ 
+ import javax.ws.rs.WebApplicationException;
+ 
+ import org.apache.nifi.action.Action;
+ import org.apache.nifi.action.component.details.ComponentDetails;
+ import org.apache.nifi.action.component.details.ProcessorDetails;
+ import org.apache.nifi.action.component.details.RemoteProcessGroupDetails;
+ import org.apache.nifi.action.details.ActionDetails;
+ import org.apache.nifi.action.details.ConfigureDetails;
+ import org.apache.nifi.action.details.ConnectDetails;
+ import org.apache.nifi.action.details.MoveDetails;
+ import org.apache.nifi.action.details.PurgeDetails;
+ import org.apache.nifi.authorization.Authority;
+ import org.apache.nifi.cluster.HeartbeatPayload;
+ import org.apache.nifi.cluster.event.Event;
+ import org.apache.nifi.cluster.node.Node;
+ import org.apache.nifi.cluster.protocol.NodeIdentifier;
+ import org.apache.nifi.components.AllowableValue;
+ import org.apache.nifi.components.PropertyDescriptor;
+ import org.apache.nifi.components.ValidationResult;
+ import org.apache.nifi.connectable.Connectable;
+ import org.apache.nifi.connectable.ConnectableType;
+ import org.apache.nifi.connectable.Connection;
+ import org.apache.nifi.connectable.Funnel;
+ import org.apache.nifi.connectable.Port;
+ import org.apache.nifi.connectable.Position;
+ import org.apache.nifi.controller.ControllerService;
+ import org.apache.nifi.controller.ControllerServiceLookup;
+ import org.apache.nifi.controller.Counter;
+ import org.apache.nifi.controller.ProcessorNode;
+ import org.apache.nifi.controller.Snippet;
+ import org.apache.nifi.controller.Template;
+ import org.apache.nifi.controller.label.Label;
+ import org.apache.nifi.controller.status.ConnectionStatus;
+ import org.apache.nifi.controller.status.PortStatus;
+ import org.apache.nifi.controller.status.ProcessGroupStatus;
+ import org.apache.nifi.controller.status.ProcessorStatus;
+ import org.apache.nifi.controller.status.RemoteProcessGroupStatus;
+ import org.apache.nifi.diagnostics.GarbageCollection;
+ import org.apache.nifi.diagnostics.StorageUsage;
+ import org.apache.nifi.diagnostics.SystemDiagnostics;
+ import org.apache.nifi.flowfile.FlowFilePrioritizer;
+ import org.apache.nifi.groups.ProcessGroup;
+ import org.apache.nifi.groups.ProcessGroupCounts;
+ import org.apache.nifi.groups.RemoteProcessGroup;
+ import org.apache.nifi.history.History;
+ import org.apache.nifi.processor.Processor;
+ import org.apache.nifi.processor.Relationship;
+ import org.apache.nifi.processor.annotation.CapabilityDescription;
+ import org.apache.nifi.processor.annotation.Tags;
+ import org.apache.nifi.provenance.lineage.ComputeLineageResult;
+ import org.apache.nifi.provenance.lineage.ComputeLineageSubmission;
+ import org.apache.nifi.provenance.lineage.LineageEdge;
+ import org.apache.nifi.provenance.lineage.LineageNode;
+ import org.apache.nifi.provenance.lineage.ProvenanceEventLineageNode;
+ import org.apache.nifi.remote.RemoteGroupPort;
+ import org.apache.nifi.remote.RootGroupPort;
+ import org.apache.nifi.reporting.Bulletin;
+ import org.apache.nifi.reporting.BulletinRepository;
+ import org.apache.nifi.scheduling.SchedulingStrategy;
+ import org.apache.nifi.user.NiFiUser;
+ import org.apache.nifi.user.NiFiUserGroup;
+ import org.apache.nifi.util.FormatUtils;
+ import org.apache.nifi.util.NiFiProperties;
+ import org.apache.nifi.web.Revision;
+ import org.apache.nifi.web.api.dto.ProcessorConfigDTO.AllowableValueDTO;
+ import org.apache.nifi.web.api.dto.ProcessorConfigDTO.PropertyDescriptorDTO;
+ import org.apache.nifi.web.api.dto.action.ActionDTO;
+ import org.apache.nifi.web.api.dto.action.HistoryDTO;
+ import org.apache.nifi.web.api.dto.action.component.details.ComponentDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.component.details.ProcessorDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.component.details.RemoteProcessGroupDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.details.ActionDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.details.ConfigureDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.details.ConnectDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.details.MoveDetailsDTO;
+ import org.apache.nifi.web.api.dto.action.details.PurgeDetailsDTO;
+ import org.apache.nifi.web.api.dto.provenance.lineage.LineageDTO;
+ import org.apache.nifi.web.api.dto.provenance.lineage.LineageRequestDTO;
+ import org.apache.nifi.web.api.dto.provenance.lineage.LineageRequestDTO.LineageRequestType;
+ import org.apache.nifi.web.api.dto.provenance.lineage.LineageResultsDTO;
+ import org.apache.nifi.web.api.dto.provenance.lineage.ProvenanceLinkDTO;
+ import org.apache.nifi.web.api.dto.provenance.lineage.ProvenanceNodeDTO;
+ import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
+ import org.apache.nifi.web.api.dto.status.PortStatusDTO;
+ import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
+ import org.apache.nifi.web.api.dto.status.ProcessorStatusDTO;
+ import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusDTO;
+ import org.apache.nifi.web.api.dto.status.StatusDTO;
+ import org.apache.commons.lang3.StringUtils;
+ 
+ /**
+  *
+  */
+ public final class DtoFactory {
+ 
+     @SuppressWarnings("rawtypes")
+     private final static Comparator<Class> CLASS_NAME_COMPARATOR = new Comparator<Class>() {
+         @Override
+         public int compare(Class class1, Class class2) {
+             return Collator.getInstance(Locale.US).compare(class1.getSimpleName(), class2.getSimpleName());
+         }
+     };
+ 
+     final int MAX_BULLETINS_PER_COMPONENT = 5;
+ 
+     private NiFiProperties properties;
+     private ControllerServiceLookup controllerServiceLookup;
+ 
+     /**
+      * Creates an ActionDTO for the specified Action.
+      *
+      * @param action
+      * @return
+      */
+     public ActionDTO createActionDto(final Action action) {
+         final ActionDTO actionDto = new ActionDTO();
+         actionDto.setId(action.getId());
+         actionDto.setSourceId(action.getSourceId());
+         actionDto.setSourceName(action.getSourceName());
+         actionDto.setSourceType(action.getSourceType().name());
+         actionDto.setTimestamp(action.getTimestamp());
+         actionDto.setUserDn(action.getUserDn());
+         actionDto.setUserName(action.getUserName());
+         actionDto.setOperation(action.getOperation().name());
+         actionDto.setActionDetails(createActionDetailsDto(action.getActionDetails()));
+         actionDto.setComponentDetails(createComponentDetailsDto(action.getComponentDetails()));
+ 
+         return actionDto;
+     }
+ 
+     /**
+      * Creates an ActionDetailsDTO for the specified ActionDetails.
+      *
+      * @param actionDetails
+      * @return
+      */
+     private ActionDetailsDTO createActionDetailsDto(final ActionDetails actionDetails) {
+         if (actionDetails == null) {
+             return null;
+         }
+ 
+         if (actionDetails instanceof ConfigureDetails) {
+             final ConfigureDetailsDTO configureDetails = new ConfigureDetailsDTO();
+             configureDetails.setName(((ConfigureDetails) actionDetails).getName());
+             configureDetails.setPreviousValue(((ConfigureDetails) actionDetails).getPreviousValue());
+             configureDetails.setValue(((ConfigureDetails) actionDetails).getValue());
+             return configureDetails;
+         } else if (actionDetails instanceof ConnectDetails) {
+             final ConnectDetailsDTO connectDetails = new ConnectDetailsDTO();
+             connectDetails.setSourceId(((ConnectDetails) actionDetails).getSourceId());
+             connectDetails.setSourceName(((ConnectDetails) actionDetails).getSourceName());
+             connectDetails.setSourceType(((ConnectDetails) actionDetails).getSourceType().toString());
+             connectDetails.setRelationship(((ConnectDetails) actionDetails).getRelationship());
+             connectDetails.setDestinationId(((ConnectDetails) actionDetails).getDestinationId());
+             connectDetails.setDestinationName(((ConnectDetails) actionDetails).getDestinationName());
+             connectDetails.setDestinationType(((ConnectDetails) actionDetails).getDestinationType().toString());
+             return connectDetails;
+         } else if (actionDetails instanceof MoveDetails) {
+             final MoveDetailsDTO moveDetails = new MoveDetailsDTO();
+             moveDetails.setPreviousGroup(((MoveDetails) actionDetails).getPreviousGroup());
+             moveDetails.setPreviousGroupId(((MoveDetails) actionDetails).getPreviousGroupId());
+             moveDetails.setGroup(((MoveDetails) actionDetails).getGroup());
+             moveDetails.setGroupId(((MoveDetails) actionDetails).getGroupId());
+             return moveDetails;
+         } else if (actionDetails instanceof PurgeDetails) {
+             final PurgeDetailsDTO purgeDetails = new PurgeDetailsDTO();
+             purgeDetails.setEndDate(((PurgeDetails) actionDetails).getEndDate());
+             return purgeDetails;
+         } else {
+             throw new WebApplicationException(new IllegalArgumentException(String.format("Unrecognized type of action details encountered %s during serialization.", actionDetails.toString())));
+         }
+     }
+ 
+     /**
+      * Creates a ComponentDetailsDTO for the specified ComponentDetails.
+      *
+      * @param componentDetails
+      * @return
+      */
+     private ComponentDetailsDTO createComponentDetailsDto(final ComponentDetails componentDetails) {
+         if (componentDetails == null) {
+             return null;
+         }
+ 
+         if (componentDetails instanceof ProcessorDetails) {
+             final ProcessorDetailsDTO processorDetails = new ProcessorDetailsDTO();
+             processorDetails.setType(((ProcessorDetails) componentDetails).getType());
+             return processorDetails;
+         } else if (componentDetails instanceof RemoteProcessGroupDetails) {
+             final RemoteProcessGroupDetailsDTO remoteProcessGroupDetails = new RemoteProcessGroupDetailsDTO();
+             remoteProcessGroupDetails.setUri(((RemoteProcessGroupDetails) componentDetails).getUri());
+             return remoteProcessGroupDetails;
+         } else {
+             throw new WebApplicationException(new IllegalArgumentException(String.format("Unrecognized type of component details encountered %s during serialization. ", componentDetails.toString())));
+         }
+     }
+ 
+     /**
+      * Creates a HistoryDTO from the specified History.
+      *
+      * @param history
+      * @return
+      */
+     public HistoryDTO createHistoryDto(final History history) {
+         final HistoryDTO historyDto = new HistoryDTO();
+         historyDto.setTotal(history.getTotal());
+         historyDto.setLastRefreshed(history.getLastRefreshed());
+ 
+         if (history.getActions() != null) {
+             List<ActionDTO> actionDtos = new ArrayList<>();
+             for (Action action : history.getActions()) {
+                 actionDtos.add(createActionDto(action));
+             }
+             historyDto.setActions(actionDtos);
+         }
+ 
+         return historyDto;
+     }
+ 
+     /**
+      * Creates CounterDTOs for each Counter specified.
+      *
+      * @param counterDtos
+      * @return
+      */
+     public CountersDTO createCountersDto(final Collection<CounterDTO> counterDtos) {
+         final CountersDTO dto = new CountersDTO();
+         dto.setCounters(counterDtos);
+         dto.setGenerated(new Date());
+         return dto;
+     }
+ 
+     /**
+      * Creates a CounterDTO from the specified Counter.
+      *
+      * @param counter
+      * @return
+      */
+     public CounterDTO createCounterDto(final Counter counter) {
+         final CounterDTO dto = new CounterDTO();
+         dto.setId(counter.getIdentifier());
+         dto.setContext(counter.getContext());
+         dto.setName(counter.getName());
+         dto.setValueCount(counter.getValue());
+         dto.setValue(FormatUtils.formatCount(counter.getValue()));
+         return dto;
+     }
+ 
+     /**
+      * Creates a PositionDTO from the specified position
+      *
+      * @param position
+      * @return
+      */
+     public PositionDTO createPositionDto(final Position position) {
+         return new PositionDTO(position.getX(), position.getY());
+     }
+ 
+     /**
+      * Creates a ConnectionDTO from the specified Connection.
+      *
+      * @param connection
+      * @return
+      */
+     public ConnectionDTO createConnectionDto(final Connection connection) {
+         if (connection == null) {
+             return null;
+         }
+         final ConnectionDTO dto = new ConnectionDTO();
+ 
+         dto.setId(connection.getIdentifier());
+         dto.setParentGroupId(connection.getProcessGroup().getIdentifier());
+ 
+         final List<PositionDTO> bendPoints = new ArrayList<>();
+         for (final Position bendPoint : connection.getBendPoints()) {
+             bendPoints.add(createPositionDto(bendPoint));
+         }
+         dto.setBends(bendPoints);
+         dto.setName(connection.getName());
+         dto.setLabelIndex(connection.getLabelIndex());
+         dto.setzIndex(connection.getZIndex());
+         dto.setSource(createConnectableDto(connection.getSource()));
+         dto.setDestination(createConnectableDto(connection.getDestination()));
+ 
+         dto.setBackPressureObjectThreshold(connection.getFlowFileQueue().getBackPressureObjectThreshold());
+         dto.setBackPressureDataSizeThreshold(connection.getFlowFileQueue().getBackPressureDataSizeThreshold());
+         dto.setFlowFileExpiration(connection.getFlowFileQueue().getFlowFileExpiration());
+         dto.setPrioritizers(new ArrayList<String>());
+         for (final FlowFilePrioritizer comparator : connection.getFlowFileQueue().getPriorities()) {
+             dto.getPrioritizers().add(comparator.getClass().getCanonicalName());
+         }
+ 
+         // For ports, we do not want to populate the relationships.
+         for (final Relationship selectedRelationship : connection.getRelationships()) {
+             if (!Relationship.ANONYMOUS.equals(selectedRelationship)) {
+                 if (dto.getSelectedRelationships() == null) {
+                     dto.setSelectedRelationships(new TreeSet<String>(Collator.getInstance(Locale.US)));
+                 }
+ 
+                 dto.getSelectedRelationships().add(selectedRelationship.getName());
+             }
+         }
+ 
+         // For ports, we do not want to populate the relationships.
+         for (final Relationship availableRelationship : connection.getSource().getRelationships()) {
+             if (!Relationship.ANONYMOUS.equals(availableRelationship)) {
+                 if (dto.getAvailableRelationships() == null) {
+                     dto.setAvailableRelationships(new TreeSet<String>(Collator.getInstance(Locale.US)));
+                 }
+ 
+                 dto.getAvailableRelationships().add(availableRelationship.getName());
+             }
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a ConnectableDTO from the specified Connectable.
+      *
+      * @param connectable
+      * @return
+      */
+     public ConnectableDTO createConnectableDto(final Connectable connectable) {
+         if (connectable == null) {
+             return null;
+         }
+ 
+         final ConnectableDTO dto = new ConnectableDTO();
+         dto.setId(connectable.getIdentifier());
+         dto.setName(connectable.getName());
+         dto.setType(connectable.getConnectableType().name());
+ 
+         if (connectable instanceof RemoteGroupPort) {
+             final RemoteGroupPort remoteGroupPort = (RemoteGroupPort) connectable;
+             final RemoteProcessGroup remoteGroup = remoteGroupPort.getRemoteProcessGroup();
+             dto.setGroupId(remoteGroup.getIdentifier());
+             dto.setRunning(remoteGroupPort.isTargetRunning());
+             dto.setTransmitting(remoteGroupPort.isRunning());
+             dto.setExists(remoteGroupPort.getTargetExists());
+             dto.setComments(remoteGroup.getComments());
+         } else {
+             dto.setGroupId(connectable.getProcessGroup().getIdentifier());
+             dto.setRunning(connectable.isRunning());
+             dto.setComments(connectable.getComments());
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a LabelDTO from the specified Label.
+      *
+      * @param label
+      * @return
+      */
+     public LabelDTO createLabelDto(final Label label) {
+         if (label == null) {
+             return null;
+         }
+ 
+         final LabelDTO dto = new LabelDTO();
+         dto.setId(label.getIdentifier());
+         dto.setPosition(createPositionDto(label.getPosition()));
+         dto.setStyle(label.getStyle());
+         dto.setHeight(label.getSize().getHeight());
+         dto.setWidth(label.getSize().getWidth());
+         dto.setLabel(label.getValue());
+         dto.setParentGroupId(label.getProcessGroup().getIdentifier());
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a FunnelDTO from the specified Funnel.
+      *
+      * @param funnel
+      * @return
+      */
+     public FunnelDTO createFunnelDto(final Funnel funnel) {
+         if (funnel == null) {
+             return null;
+         }
+ 
+         final FunnelDTO dto = new FunnelDTO();
+         dto.setId(funnel.getIdentifier());
+         dto.setPosition(createPositionDto(funnel.getPosition()));
+         dto.setParentGroupId(funnel.getProcessGroup().getIdentifier());
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a SnippetDTO from the specified Snippet.
+      *
+      * @param snippet
+      * @return
+      */
+     public SnippetDTO createSnippetDto(final Snippet snippet) {
+         final SnippetDTO dto = new SnippetDTO();
+         dto.setId(snippet.getId());
+         dto.setParentGroupId(snippet.getParentGroupId());
+         dto.setLinked(snippet.isLinked());
+ 
+         // populate the snippet contents ids
+         dto.setConnections(copy(snippet.getConnections()));
+         dto.setFunnels(copy(snippet.getFunnels()));
+         dto.setInputPorts(copy(snippet.getInputPorts()));
+         dto.setLabels(copy(snippet.getLabels()));
+         dto.setOutputPorts(copy(snippet.getOutputPorts()));
+         dto.setProcessGroups(copy(snippet.getProcessGroups()));
+         dto.setProcessors(copy(snippet.getProcessors()));
+         dto.setRemoteProcessGroups(copy(snippet.getRemoteProcessGroups()));
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a TemplateDTO from the specified template.
+      *
+      * @param template
+      * @return
+      */
+     public TemplateDTO createTemplateDTO(final Template template) {
+         if (template == null) {
+             return null;
+         }
+ 
+         final TemplateDTO original = template.getDetails();
+ 
+         final TemplateDTO copy = new TemplateDTO();
+         copy.setId(original.getId());
+         copy.setName(original.getName());
+         copy.setDescription(original.getDescription());
+         copy.setTimestamp(original.getTimestamp());
+         copy.setUri(original.getUri());
+         return copy;
+     }
+ 
+     private String formatCount(final Integer intStatus) {
+         return intStatus == null ? "-" : FormatUtils.formatCount(intStatus);
+     }
+ 
+     private String formatDataSize(final Long longStatus) {
+         return longStatus == null ? "-" : FormatUtils.formatDataSize(longStatus);
+     }
+ 
+     public RemoteProcessGroupStatusDTO createRemoteProcessGroupStatusDto(final RemoteProcessGroupStatus remoteProcessGroupStatus) {
+         final RemoteProcessGroupStatusDTO dto = new RemoteProcessGroupStatusDTO();
+         dto.setId(remoteProcessGroupStatus.getId());
+         dto.setGroupId(remoteProcessGroupStatus.getGroupId());
+         dto.setTargetUri(remoteProcessGroupStatus.getTargetUri());
+         dto.setName(remoteProcessGroupStatus.getName());
+         dto.setTransmissionStatus(remoteProcessGroupStatus.getTransmissionStatus().toString());
+         dto.setActiveThreadCount(remoteProcessGroupStatus.getActiveThreadCount());
+         dto.setSent(formatCount(remoteProcessGroupStatus.getSentCount()) + " / " + formatDataSize(remoteProcessGroupStatus.getSentContentSize()));
+         dto.setReceived(formatCount(remoteProcessGroupStatus.getReceivedCount()) + " / " + formatDataSize(remoteProcessGroupStatus.getReceivedContentSize()));
+         dto.setAuthorizationIssues(remoteProcessGroupStatus.getAuthorizationIssues());
+ 
+         return dto;
+     }
+ 
+     public ProcessGroupStatusDTO createProcessGroupStatusDto(final BulletinRepository bulletinRepository, final ProcessGroupStatus processGroupStatus) {
+ 
+         final ProcessGroupStatusDTO processGroupStatusDto = new ProcessGroupStatusDTO();
+         processGroupStatusDto.setId(processGroupStatus.getId());
+         processGroupStatusDto.setName(processGroupStatus.getName());
+         processGroupStatusDto.setStatsLastRefreshed(new Date(processGroupStatus.getCreationTimestamp()));
+         processGroupStatusDto.setQueued(formatCount(processGroupStatus.getQueuedCount()) + " / " + formatDataSize(processGroupStatus.getQueuedContentSize()));
+         processGroupStatusDto.setRead(formatDataSize(processGroupStatus.getBytesRead()));
+         processGroupStatusDto.setWritten(formatDataSize(processGroupStatus.getBytesWritten()));
+         processGroupStatusDto.setInput(formatCount(processGroupStatus.getInputCount()) + " / " + formatDataSize(processGroupStatus.getInputContentSize()));
+         processGroupStatusDto.setOutput(formatCount(processGroupStatus.getOutputCount()) + " / " + formatDataSize(processGroupStatus.getOutputContentSize()));
+         processGroupStatusDto.setActiveThreadCount(processGroupStatus.getActiveThreadCount());
+ 
+         final Map<String, StatusDTO> componentStatusDtoMap = new HashMap<>();
+ 
+         // processor status
+         final Collection<ProcessorStatusDTO> processorStatDtoCollection = new ArrayList<>();
+         processGroupStatusDto.setProcessorStatus(processorStatDtoCollection);
+         final Collection<ProcessorStatus> processorStatusCollection = processGroupStatus.getProcessorStatus();
+         if (processorStatusCollection != null) {
+             for (final ProcessorStatus processorStatus : processorStatusCollection) {
+                 final ProcessorStatusDTO processorStatusDto = createProcessorStatusDto(processorStatus);
+                 processorStatDtoCollection.add(processorStatusDto);
+                 componentStatusDtoMap.put(processorStatusDto.getId(), processorStatusDto);
+             }
+         }
+ 
+         // connection status
+         final Collection<ConnectionStatusDTO> connectionStatusDtoCollection = new ArrayList<>();
+         processGroupStatusDto.setConnectionStatus(connectionStatusDtoCollection);
+         final Collection<ConnectionStatus> connectionStatusCollection = processGroupStatus.getConnectionStatus();
+         if (connectionStatusCollection != null) {
+             for (final ConnectionStatus connectionStatus : connectionStatusCollection) {
+                 final ConnectionStatusDTO connectionStatusDto = createConnectionStatusDto(connectionStatus);
+                 connectionStatusDtoCollection.add(connectionStatusDto);
+             }
+         }
+ 
+         // local child process groups
+         final Collection<ProcessGroupStatusDTO> childProcessGroupStatusDtoCollection = new ArrayList<>();
+         processGroupStatusDto.setProcessGroupStatus(childProcessGroupStatusDtoCollection);
+         final Collection<ProcessGroupStatus> childProcessGroupStatusCollection = processGroupStatus.getProcessGroupStatus();
+         if (childProcessGroupStatusCollection != null) {
+             for (final ProcessGroupStatus childProcessGroupStatus : childProcessGroupStatusCollection) {
+                 final ProcessGroupStatusDTO childProcessGroupStatusDto = createProcessGroupStatusDto(bulletinRepository, childProcessGroupStatus);
+                 childProcessGroupStatusDtoCollection.add(childProcessGroupStatusDto);
+             }
+         }
+ 
+         // remote child process groups
+         final Collection<RemoteProcessGroupStatusDTO> childRemoteProcessGroupStatusDtoCollection = new ArrayList<>();
+         processGroupStatusDto.setRemoteProcessGroupStatus(childRemoteProcessGroupStatusDtoCollection);
+         final Collection<RemoteProcessGroupStatus> childRemoteProcessGroupStatusCollection = processGroupStatus.getRemoteProcessGroupStatus();
+         if (childRemoteProcessGroupStatusCollection != null) {
+             for (final RemoteProcessGroupStatus childRemoteProcessGroupStatus : childRemoteProcessGroupStatusCollection) {
+                 final RemoteProcessGroupStatusDTO childRemoteProcessGroupStatusDto = createRemoteProcessGroupStatusDto(childRemoteProcessGroupStatus);
+                 childRemoteProcessGroupStatusDtoCollection.add(childRemoteProcessGroupStatusDto);
+                 componentStatusDtoMap.put(childRemoteProcessGroupStatusDto.getId(), childRemoteProcessGroupStatusDto);
+             }
+         }
+ 
+         // input ports
+         final Collection<PortStatusDTO> inputPortStatusDtoCollection = new ArrayList<>();
+         processGroupStatusDto.setInputPortStatus(inputPortStatusDtoCollection);
+         final Collection<PortStatus> inputPortStatusCollection = processGroupStatus.getInputPortStatus();
+         if (inputPortStatusCollection != null) {
+             for (final PortStatus portStatus : inputPortStatusCollection) {
+                 final PortStatusDTO portStatusDto = createPortStatusDto(portStatus);
+                 inputPortStatusDtoCollection.add(portStatusDto);
+                 componentStatusDtoMap.put(portStatusDto.getId(), portStatusDto);
+             }
+         }
+ 
+         // output ports
+         final Collection<PortStatusDTO> outputPortStatusDtoCollection = new ArrayList<>();
+         processGroupStatusDto.setOutputPortStatus(outputPortStatusDtoCollection);
+         final Collection<PortStatus> outputPortStatusCollection = processGroupStatus.getOutputPortStatus();
+         if (outputPortStatusCollection != null) {
+             for (final PortStatus portStatus : outputPortStatusCollection) {
+                 final PortStatusDTO portStatusDto = createPortStatusDto(portStatus);
+                 outputPortStatusDtoCollection.add(portStatusDto);
+                 componentStatusDtoMap.put(portStatusDto.getId(), portStatusDto);
+             }
+         }
+ 
+         // get the bulletins for this group and associate with the specific child component
+         if (bulletinRepository != null) {
+             if (processGroupStatusDto.getBulletins() == null) {
+                 processGroupStatusDto.setBulletins(new ArrayList<BulletinDTO>());
+             }
+ 
+             // locate bulletins for this process group
+             final List<Bulletin> results = bulletinRepository.findBulletinsForGroupBySource(processGroupStatus.getId(), MAX_BULLETINS_PER_COMPONENT);
+             for (final Bulletin bulletin : results) {
+                 final StatusDTO status = componentStatusDtoMap.get(bulletin.getSourceId());
+ 
+                 // ensure this connectable is still in the flow
+                 if (status != null) {
+                     if (status.getBulletins() == null) {
+                         status.setBulletins(new ArrayList<BulletinDTO>());
+                     }
+ 
+                     // convert the result into a dto
+                     final BulletinDTO bulletinDto = createBulletinDto(bulletin);
+                     status.getBulletins().add(bulletinDto);
+ 
+                     // create a copy for the parent group
+                     final BulletinDTO copy = copy(bulletinDto);
+                     copy.setGroupId(StringUtils.EMPTY);
+                     copy.setSourceId(processGroupStatus.getId());
+                     copy.setSourceName(processGroupStatus.getName());
+                     processGroupStatusDto.getBulletins().add(copy);
+                 }
+             }
+ 
+             // copy over descendant bulletins
+             for (final ProcessGroupStatusDTO childProcessGroupStatusDto : processGroupStatusDto.getProcessGroupStatus()) {
+                 if (childProcessGroupStatusDto.getBulletins() != null) {
+                     for (final BulletinDTO descendantBulletinDto : childProcessGroupStatusDto.getBulletins()) {
+                         // create a copy for the parent group
+                         final BulletinDTO copy = copy(descendantBulletinDto);
+                         copy.setGroupId(StringUtils.EMPTY);
+                         copy.setSourceId(processGroupStatus.getId());
+                         copy.setSourceName(processGroupStatus.getName());
+                         processGroupStatusDto.getBulletins().add(copy);
+                     }
+                 }
+             }
+ 
+             // sort the bulletins
+             Collections.sort(processGroupStatusDto.getBulletins(), new Comparator<BulletinDTO>() {
+                 @Override
+                 public int compare(BulletinDTO o1, BulletinDTO o2) {
+                     if (o1 == null && o2 == null) {
+                         return 0;
+                     }
+                     if (o1 == null) {
+                         return 1;
+                     }
+                     if (o2 == null) {
+                         return -1;
+                     }
+ 
+                     return -Long.compare(o1.getId(), o2.getId());
+                 }
+             });
+ 
+             // prune the response to only include the max number of bulletins
+             if (processGroupStatusDto.getBulletins().size() > MAX_BULLETINS_PER_COMPONENT) {
+                 processGroupStatusDto.setBulletins(processGroupStatusDto.getBulletins().subList(0, MAX_BULLETINS_PER_COMPONENT));
+             }
+         }
+ 
+         return processGroupStatusDto;
+     }
+ 
+     public ConnectionStatusDTO createConnectionStatusDto(final ConnectionStatus connectionStatus) {
+ 
+         final ConnectionStatusDTO connectionStatusDto = new ConnectionStatusDTO();
+         connectionStatusDto.setGroupId(connectionStatus.getGroupId());
+         connectionStatusDto.setId(connectionStatus.getId());
+         connectionStatusDto.setName(connectionStatus.getName());
+         connectionStatusDto.setSourceId(connectionStatus.getSourceId());
+         connectionStatusDto.setSourceName(connectionStatus.getSourceName());
+         connectionStatusDto.setDestinationId(connectionStatus.getDestinationId());
+         connectionStatusDto.setDestinationName(connectionStatus.getDestinationName());
+ 
+         final String queuedCount = FormatUtils.formatCount(connectionStatus.getQueuedCount());
+         final String queuedSize = FormatUtils.formatDataSize(connectionStatus.getQueuedBytes());
+         connectionStatusDto.setQueuedCount(queuedCount);
+         connectionStatusDto.setQueuedSize(queuedSize);
+         connectionStatusDto.setQueued(queuedCount + " / " + queuedSize);
+ 
+         final int inputCount = connectionStatus.getInputCount();
+         final long inputBytes = connectionStatus.getInputBytes();
+         connectionStatusDto.setInput(FormatUtils.formatCount(inputCount) + " / " + FormatUtils.formatDataSize(inputBytes));
+ 
+         final int outputCount = connectionStatus.getOutputCount();
+         final long outputBytes = connectionStatus.getOutputBytes();
+         connectionStatusDto.setOutput(FormatUtils.formatCount(outputCount) + " / " + FormatUtils.formatDataSize(outputBytes));
+ 
+         return connectionStatusDto;
+     }
+ 
+     public ProcessorStatusDTO createProcessorStatusDto(final ProcessorStatus procStatus) {
+ 
+         final ProcessorStatusDTO dto = new ProcessorStatusDTO();
+         dto.setId(procStatus.getId());
+         dto.setGroupId(procStatus.getGroupId());
+         dto.setName(procStatus.getName());
+ 
+         final int processedCount = procStatus.getOutputCount();
+         final long numProcessedBytes = procStatus.getOutputBytes();
+         dto.setOutput(FormatUtils.formatCount(processedCount) + " / " + FormatUtils.formatDataSize(numProcessedBytes));
+ 
+         final int inputCount = procStatus.getInputCount();
+         final long inputBytes = procStatus.getInputBytes();
+         dto.setInput(FormatUtils.formatCount(inputCount) + " / " + FormatUtils.formatDataSize(inputBytes));
+ 
+         final long readBytes = procStatus.getBytesRead();
+         dto.setRead(FormatUtils.formatDataSize(readBytes));
+ 
+         final long writtenBytes = procStatus.getBytesWritten();
+         dto.setWritten(FormatUtils.formatDataSize(writtenBytes));
+ 
+         dto.setTasksDuration(FormatUtils.formatHoursMinutesSeconds(procStatus.getProcessingNanos(), TimeUnit.NANOSECONDS));
+         dto.setTasks(FormatUtils.formatCount(procStatus.getInvocations()));
+ 
+         // determine the run status
+         dto.setRunStatus(procStatus.getRunStatus().toString());
+ 
+         dto.setActiveThreadCount(procStatus.getActiveThreadCount());
+         dto.setType(procStatus.getType());
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a PortStatusDTO for the specified PortStatus.
+      *
+      * @param portStatus
+      * @return
+      */
+     public PortStatusDTO createPortStatusDto(final PortStatus portStatus) {
+         final PortStatusDTO dto = new PortStatusDTO();
+         dto.setId(portStatus.getId());
+         dto.setGroupId(portStatus.getGroupId());
+         dto.setName(portStatus.getName());
+         dto.setActiveThreadCount(portStatus.getActiveThreadCount());
+         dto.setRunStatus(portStatus.getRunStatus().toString());
+         dto.setTransmitting(portStatus.isTransmitting());
+ 
+         final int processedCount = portStatus.getOutputCount();
+         final long numProcessedBytes = portStatus.getOutputBytes();
+         dto.setOutput(FormatUtils.formatCount(processedCount) + " / " + FormatUtils.formatDataSize(numProcessedBytes));
+ 
+         final int inputCount = portStatus.getInputCount();
+         final long inputBytes = portStatus.getInputBytes();
+         dto.setInput(FormatUtils.formatCount(inputCount) + " / " + FormatUtils.formatDataSize(inputBytes));
+ 
+         return dto;
+     }
+ 
+     /**
+      * Copies the specified snippet.
+      *
+      * @param originalSnippet
+      * @return
+      */
+     public FlowSnippetDTO copySnippetContents(FlowSnippetDTO originalSnippet) {
+         final FlowSnippetDTO copySnippet = new FlowSnippetDTO();
+ 
+         if (originalSnippet.getConnections() != null) {
+             for (final ConnectionDTO connection : originalSnippet.getConnections()) {
+                 copySnippet.getConnections().add(copy(connection));
+             }
+         }
+         if (originalSnippet.getInputPorts() != null) {
+             for (final PortDTO port : originalSnippet.getInputPorts()) {
+                 copySnippet.getInputPorts().add(copy(port));
+             }
+         }
+         if (originalSnippet.getOutputPorts() != null) {
+             for (final PortDTO port : originalSnippet.getOutputPorts()) {
+                 copySnippet.getOutputPorts().add(copy(port));
+             }
+         }
+         if (originalSnippet.getProcessGroups() != null) {
+             for (final ProcessGroupDTO processGroup : originalSnippet.getProcessGroups()) {
+                 copySnippet.getProcessGroups().add(copy(processGroup, true));
+             }
+         }
+         if (originalSnippet.getProcessors() != null) {
+             for (final ProcessorDTO processor : originalSnippet.getProcessors()) {
+                 copySnippet.getProcessors().add(copy(processor));
+             }
+         }
+         if (originalSnippet.getLabels() != null) {
+             for (final LabelDTO label : originalSnippet.getLabels()) {
+                 copySnippet.getLabels().add(copy(label));
+             }
+         }
+         if (originalSnippet.getFunnels() != null) {
+             for (final FunnelDTO funnel : originalSnippet.getFunnels()) {
+                 copySnippet.getFunnels().add(copy(funnel));
+             }
+         }
+         if (originalSnippet.getRemoteProcessGroups() != null) {
+             for (final RemoteProcessGroupDTO remoteGroup : originalSnippet.getRemoteProcessGroups()) {
+                 copySnippet.getRemoteProcessGroups().add(copy(remoteGroup));
+             }
+         }
+ 
+         return copySnippet;
+     }
+ 
+     /**
+      * Creates a PortDTO from the specified Port.
+      *
+      * @param port
+      * @return
+      */
+     public PortDTO createPortDto(final Port port) {
+         if (port == null) {
+             return null;
+         }
+ 
+         final PortDTO dto = new PortDTO();
+         dto.setId(port.getIdentifier());
+         dto.setPosition(createPositionDto(port.getPosition()));
+         dto.setName(port.getName());
+         dto.setComments(port.getComments());
+         dto.setConcurrentlySchedulableTaskCount(port.getMaxConcurrentTasks());
+         dto.setParentGroupId(port.getProcessGroup().getIdentifier());
+         dto.setState(port.getScheduledState().toString());
+         dto.setType(port.getConnectableType().name());
+ 
+         // if this port is on the root group, determine if its actually connected to another nifi
+         if (port instanceof RootGroupPort) {
+             final RootGroupPort rootGroupPort = (RootGroupPort) port;
+             dto.setTransmitting(rootGroupPort.isTransmitting());
+             dto.setGroupAccessControl(rootGroupPort.getGroupAccessControl());
+             dto.setUserAccessControl(rootGroupPort.getUserAccessControl());
+         }
+ 
+         final Collection<ValidationResult> validationErrors = port.getValidationErrors();
+         if (validationErrors != null && !validationErrors.isEmpty()) {
+             final List<String> errors = new ArrayList<>();
+             for (final ValidationResult validationResult : validationErrors) {
+                 errors.add(validationResult.toString());
+             }
+ 
+             dto.setValidationErrors(errors);
+         }
+ 
+         return dto;
+     }
+ 
+     public RemoteProcessGroupPortDTO createRemoteProcessGroupPortDto(final RemoteGroupPort port) {
+         if (port == null) {
+             return null;
+         }
+ 
+         final RemoteProcessGroupPortDTO dto = new RemoteProcessGroupPortDTO();
+         dto.setId(port.getIdentifier());
+         dto.setName(port.getName());
+         dto.setComments(port.getComments());
+         dto.setTransmitting(port.isRunning());
+         dto.setTargetRunning(port.isTargetRunning());
+         dto.setConcurrentlySchedulableTaskCount(port.getMaxConcurrentTasks());
+         dto.setUseCompression(port.isUseCompression());
+         dto.setExists(port.getTargetExists());
+ 
+         // determine if this port is currently connected to another component locally
+         if (ConnectableType.REMOTE_OUTPUT_PORT.equals(port.getConnectableType())) {
+             dto.setConnected(!port.getConnections().isEmpty());
+         } else {
+             dto.setConnected(port.hasIncomingConnection());
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a RemoteProcessGroupDTO from the specified RemoteProcessGroup.
+      *
+      * @param group
+      * @return
+      */
+     public RemoteProcessGroupDTO createRemoteProcessGroupDto(final RemoteProcessGroup group) {
+         if (group == null) {
+             return null;
+         }
+ 
+         final Set<RemoteProcessGroupPortDTO> inputPorts = new TreeSet<>(new DtoFactory.SortedRemoteGroupPortComparator());
+         final Set<RemoteProcessGroupPortDTO> outputPorts = new TreeSet<>(new DtoFactory.SortedRemoteGroupPortComparator());
+ 
+         int activeRemoteInputPortCount = 0;
+         int inactiveRemoteInputPortCount = 0;
+         for (final Port port : group.getInputPorts()) {
+             inputPorts.add(createRemoteProcessGroupPortDto((RemoteGroupPort) port));
+ 
+             if (port.hasIncomingConnection()) {
+                 if (port.isRunning()) {
+                     activeRemoteInputPortCount++;
+                 } else {
+                     inactiveRemoteInputPortCount++;
+                 }
+             }
+         }
+ 
+         int activeRemoteOutputPortCount = 0;
+         int inactiveRemoteOutputPortCount = 0;
+         for (final Port port : group.getOutputPorts()) {
+             outputPorts.add(createRemoteProcessGroupPortDto((RemoteGroupPort) port));
+ 
+             if (!port.getConnections().isEmpty()) {
+                 if (port.isRunning()) {
+                     activeRemoteOutputPortCount++;
+                 } else {
+                     activeRemoteOutputPortCount++;
+                 }
+             }
+         }
+ 
+         final RemoteProcessGroupContentsDTO contents = new RemoteProcessGroupContentsDTO();
+         contents.setInputPorts(inputPorts);
+         contents.setOutputPorts(outputPorts);
+ 
+         final RemoteProcessGroupDTO dto = new RemoteProcessGroupDTO();
+         dto.setId(group.getIdentifier());
+         dto.setName(group.getName());
+         dto.setPosition(createPositionDto(group.getPosition()));
+         dto.setComments(group.getComments());
+         dto.setTransmitting(group.isTransmitting());
+         dto.setCommunicationsTimeout(group.getCommunicationsTimeout());
+         dto.setYieldDuration(group.getYieldDuration());
+         dto.setParentGroupId(group.getProcessGroup().getIdentifier());
+         dto.setTargetUri(group.getTargetUri().toString());
+         dto.setFlowRefreshed(group.getLastRefreshTime());
+         dto.setContents(contents);
+ 
+         // only specify the secure flag if we know the target system has site to site enabled
+         if (group.isSiteToSiteEnabled()) {
+             dto.setTargetSecure(group.getSecureFlag());
+         }
+ 
+         if (group.getAuthorizationIssue() != null) {
+             dto.setAuthorizationIssues(Arrays.asList(group.getAuthorizationIssue()));
+         }
+ 
+         dto.setActiveRemoteInputPortCount(activeRemoteInputPortCount);
+         dto.setInactiveRemoteInputPortCount(inactiveRemoteInputPortCount);
+         dto.setActiveRemoteOutputPortCount(activeRemoteOutputPortCount);
+         dto.setInactiveRemoteOutputPortCount(inactiveRemoteOutputPortCount);
+ 
+         final ProcessGroupCounts counts = group.getCounts();
+         if (counts != null) {
+             dto.setInputPortCount(counts.getInputPortCount());
+             dto.setOutputPortCount(counts.getOutputPortCount());
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProcessGroupDTO from the specified parent ProcessGroup.
+      *
+      * @param parentGroup
+      * @return
+      */
+     private ProcessGroupDTO createParentProcessGroupDto(final ProcessGroup parentGroup) {
+         if (parentGroup == null) {
+             return null;
+         }
+ 
+         final ProcessGroupDTO dto = new ProcessGroupDTO();
+         dto.setId(parentGroup.getIdentifier());
+         dto.setName(parentGroup.getName());
+ 
+         if (parentGroup.getParent() != null) {
+             dto.setParent(createParentProcessGroupDto(parentGroup.getParent()));
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProcessGroupDTO from the specified ProcessGroup.
+      *
+      * @param group
+      * @return
+      */
+     public ProcessGroupDTO createProcessGroupDto(final ProcessGroup group) {
+         return createProcessGroupDto(group, false);
+     }
+ 
+     /**
+      * Creates a ProcessGroupDTO from the specified ProcessGroup.
+      *
+      * @param group
+      * @param recurse
+      * @return
+      */
+     public ProcessGroupDTO createProcessGroupDto(final ProcessGroup group, final boolean recurse) {
+         final ProcessGroupDTO dto = createConciseProcessGroupDto(group);
+         dto.setContents(createProcessGroupContentsDto(group, recurse));
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProcessGroupDTO from the specified ProcessGroup.
+      *
+      * @param group
+      * @param recurse
+      * @return
+      */
+     private ProcessGroupDTO createConciseProcessGroupDto(final ProcessGroup group) {
+         if (group == null) {
+             return null;
+         }
+ 
+         final ProcessGroupDTO dto = new ProcessGroupDTO();
+         dto.setId(group.getIdentifier());
+         dto.setPosition(createPositionDto(group.getPosition()));
+         dto.setComments(group.getComments());
+         dto.setName(group.getName());
+ 
+         ProcessGroup parentGroup = group.getParent();
+         if (parentGroup != null) {
+             dto.setParentGroupId(parentGroup.getIdentifier());
+             dto.setParent(createParentProcessGroupDto(parentGroup));
+         }
+ 
+         final ProcessGroupCounts counts = group.getCounts();
+         dto.setRunningCount(counts.getRunningCount());
+         dto.setStoppedCount(counts.getStoppedCount());
+         dto.setInvalidCount(counts.getInvalidCount());
+         dto.setDisabledCount(counts.getDisabledCount());
+         dto.setInputPortCount(counts.getInputPortCount());
+         dto.setOutputPortCount(counts.getOutputPortCount());
+         dto.setActiveRemotePortCount(counts.getActiveRemotePortCount());
+         dto.setInactiveRemotePortCount(counts.getInactiveRemotePortCount());
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProcessGroupContentDTO from the specified ProcessGroup.
+      *
+      * @param group
+      * @param recurse
+      * @return
+      */
+     private FlowSnippetDTO createProcessGroupContentsDto(final ProcessGroup group, final boolean recurse) {
+         if (group == null) {
+             return null;
+         }
+ 
+         final FlowSnippetDTO dto = new FlowSnippetDTO();
+ 
+         for (final ProcessorNode procNode : group.getProcessors()) {
+             dto.getProcessors().add(createProcessorDto(procNode));
+         }
+ 
+         for (final Connection connNode : group.getConnections()) {
+             dto.getConnections().add(createConnectionDto(connNode));
+         }
+ 
+         for (final Label label : group.getLabels()) {
+             dto.getLabels().add(createLabelDto(label));
+         }
+ 
+         for (final Funnel funnel : group.getFunnels()) {
+             dto.getFunnels().add(createFunnelDto(funnel));
+         }
+ 
+         for (final ProcessGroup childGroup : group.getProcessGroups()) {
+             if (recurse) {
+                 dto.getProcessGroups().add(createProcessGroupDto(childGroup, recurse));
+             } else {
+                 dto.getProcessGroups().add(createConciseProcessGroupDto(childGroup));
+             }
+         }
+ 
+         for (final RemoteProcessGroup remoteProcessGroup : group.getRemoteProcessGroups()) {
+             dto.getRemoteProcessGroups().add(createRemoteProcessGroupDto(remoteProcessGroup));
+         }
+ 
+         for (final Port inputPort : group.getInputPorts()) {
+             dto.getInputPorts().add(createPortDto(inputPort));
+         }
+ 
+         for (final Port outputPort : group.getOutputPorts()) {
+             dto.getOutputPorts().add(createPortDto(outputPort));
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Gets the capability description from the specified class.
+      *
+      * @param cls
+      * @return
+      */
+     private String getCapabilityDescription(final Class<?> cls) {
+         final CapabilityDescription description = cls.getAnnotation(CapabilityDescription.class);
+         return (description == null) ? null : description.value();
+     }
+ 
+     /**
+      * Gets the tags from the specified class.
+      *
+      * @param cls
+      * @return
+      */
+     private Set<String> getTags(final Class<?> cls) {
+         final Set<String> tags = new HashSet<>();
+         final Tags tagsAnnotation = cls.getAnnotation(Tags.class);
+         if (tagsAnnotation != null) {
+             for (final String tag : tagsAnnotation.value()) {
+                 tags.add(tag);
+             }
+         }
+ 
+         return tags;
+     }
+ 
+     /**
+      * Gets the DocumentedTypeDTOs from the specified classes.
+      *
+      * @param classes
+      * @return
+      */
+     @SuppressWarnings("rawtypes")
+     public Set<DocumentedTypeDTO> fromDocumentedTypes(final Set<Class> classes) {
+         final Set<DocumentedTypeDTO> types = new LinkedHashSet<>();
+         final Set<Class> sortedClasses = new TreeSet<>(CLASS_NAME_COMPARATOR);
+         sortedClasses.addAll(classes);
+ 
+         for (final Class<?> cls : sortedClasses) {
+             final DocumentedTypeDTO type = new DocumentedTypeDTO();
+             type.setType(cls.getName());
+             type.setDescription(getCapabilityDescription(cls));
+             type.setTags(getTags(cls));
+             types.add(type);
+         }
+ 
+         return types;
+     }
++    
++    /**
++     * Identifies all baseTypes for the specified type that are assignable to the specified baseType.
++     * 
++     * @param baseType
++     * @param type
++     * @param baseTypes 
++     */
++    private void identifyBaseTypes(final Class baseType, final Class type, final Set<Class> baseTypes, final  boolean recurse) {
++        final Class[] interfaces = type.getInterfaces();
++        for (final Class i : interfaces) {
++            if (baseType.isAssignableFrom(i) && !baseType.equals(i)) {
++                baseTypes.add(i);
++            }
++        }
++        
++        if (recurse) {
++            if (type.getSuperclass() != null) {
++                identifyBaseTypes(baseType, type.getSuperclass(), baseTypes, recurse);
++            }
++        }
++    }
++    
++    /**
++     * Gets the DocumentedTypeDTOs from the specified classes for the specified baseClass.
++     *
++     * @param baseClass
++     * @param classes
++     * @return
++     */
++    public Set<DocumentedTypeDTO> fromDocumentedTypes(final Class baseClass, final Set<Class> classes) {
++        final Set<DocumentedTypeDTO> types = new LinkedHashSet<>();
++        final Set<Class> sortedClasses = new TreeSet<>(CLASS_NAME_COMPARATOR);
++        sortedClasses.addAll(classes);
++        
++        // identify all interfaces that extend baseClass for all classes
++        final Set<Class> interfaces = new HashSet<>();
++        for (final Class<?> cls : sortedClasses) {
++            identifyBaseTypes(baseClass, cls, interfaces, true);
++        }
++        
++        // build a lookup of all interfaces
++        final Map<Class, DocumentedTypeDTO> lookup = new HashMap<>();
++        
++        // convert the interfaces to DTO form
++        for (final Class<?> i : interfaces) {
++            final DocumentedTypeDTO type = new DocumentedTypeDTO();
++            type.setType(i.getName());
++            type.setDescription(getCapabilityDescription(i));
++            type.setTags(getTags(i));
++            type.setChildTypes(new LinkedHashSet<DocumentedTypeDTO>());
++            lookup.put(i, type);
++        }
++        
++        // move the interfaces into the appropriate hierarchy
++        for (final Class<?> i : interfaces) {
++            // identify the base types
++            final Set<Class> baseTypes = new LinkedHashSet<>();
++            identifyBaseTypes(baseClass, i, baseTypes, false);
++            
++            // move this interfaces into the hierarchy where appropriate
++            if (!baseTypes.isEmpty()) {
++                // get the DTO for each base type
++                for (final Class baseType : baseTypes) {
++                    final DocumentedTypeDTO parentInteface = lookup.get(baseType);
++                    final DocumentedTypeDTO childInterface = lookup.get(i);
++                    
++                    // include all parent tags in the respective children
++                    childInterface.getTags().addAll(parentInteface.getTags());
++                    
++                    // update the hierarchy
++                    parentInteface.getChildTypes().add(childInterface);
++                }
++                
++                // remove this interface from the lookup (this will only
++                // leave the interfaces that are ancestor roots)
++                lookup.remove(i);
++            }
++        }
++
++        // include the interfaces
++        sortedClasses.addAll(lookup.keySet());
++        
++        // get the DTO form for all interfaces and classes
++        for (final Class<?> cls : sortedClasses) {
++            boolean add = false;
++            
++            final DocumentedTypeDTO type;
++            if (lookup.containsKey(cls)) {
++                type = lookup.get(cls);
++                add = true;
++            } else {
++                type = new DocumentedTypeDTO();
++                type.setType(cls.getName());
++                type.setDescription(getCapabilityDescription(cls));
++                type.setTags(getTags(cls));
++            }
++            
++            // identify the base types
++            final Set<Class> baseTypes = new LinkedHashSet<>();
++            identifyBaseTypes(baseClass, cls, baseTypes, false);
++            
++            // include this type if it doesn't belong to another hierarchy
++            if (baseTypes.isEmpty()) {
++                add = true;
++            } else {
++                // get the DTO for each base type
++                for (final Class baseType : baseTypes) {
++                    final DocumentedTypeDTO parentInteface = lookup.get(baseType);
++
++                    // include all parent tags in the respective children
++                    type.getTags().addAll(parentInteface.getTags());
++
++                    // update the hierarchy
++                    parentInteface.getChildTypes().add(type);
++                }
++            }
++            
++            // add if appropriate
++            if (add) {
++                types.add(type);
++            }
++        }
++
++        return types;
++    }
+ 
+     /**
+      * Creates a ProcessorDTO from the specified ProcessorNode.
+      *
+      * @param node
+      * @return
+      */
+     public ProcessorDTO createProcessorDto(final ProcessorNode node) {
+         if (node == null) {
+             return null;
+         }
+ 
+         final ProcessorDTO dto = new ProcessorDTO();
+         dto.setId(node.getIdentifier());
+         dto.setPosition(createPositionDto(node.getPosition()));
+         dto.setStyle(node.getStyle());
+         dto.setParentGroupId(node.getProcessGroup().getIdentifier());
+ 
+         dto.setType(node.getProcessor().getClass().getCanonicalName());
+         dto.setName(node.getName());
+         dto.setState(node.getScheduledState().toString());
+ 
+         // build the relationship dtos
+         final List<RelationshipDTO> relationships = new ArrayList<>();
+         for (final Relationship rel : node.getRelationships()) {
+             final RelationshipDTO relationshipDTO = new RelationshipDTO();
+             relationshipDTO.setDescription(rel.getDescription());
+             relationshipDTO.setName(rel.getName());
+             relationshipDTO.setAutoTerminate(node.isAutoTerminated(rel));
+             relationships.add(relationshipDTO);
+         }
+ 
+         // sort the relationships
+         Collections.sort(relationships, new Comparator<RelationshipDTO>() {
+             @Override
+             public int compare(RelationshipDTO r1, RelationshipDTO r2) {
+                 return Collator.getInstance(Locale.US).compare(r1.getName(), r2.getName());
+             }
+         });
+ 
+         // set the relationships
+         dto.setRelationships(relationships);
+ 
+         dto.setDescription(getCapabilityDescription(node.getClass()));
+         dto.setSupportsParallelProcessing(!node.isTriggeredSerially());
+         dto.setSupportsEventDriven(node.isEventDrivenSupported());
+         dto.setConfig(createProcessorConfigDto(node));
+ 
+         final Collection<ValidationResult> validationErrors = node.getValidationErrors();
+         if (validationErrors != null && !validationErrors.isEmpty()) {
+             final List<String> errors = new ArrayList<>();
+             for (final ValidationResult validationResult : validationErrors) {
+                 errors.add(validationResult.toString());
+             }
+ 
+             dto.setValidationErrors(errors);
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a BulletinBoardDTO for the specified bulletins.
+      *
+      * @param bulletins
+      * @return
+      */
+     public BulletinBoardDTO createBulletinBoardDto(final List<BulletinDTO> bulletins) {
+         // sort the bulletins
+         Collections.sort(bulletins, new Comparator<BulletinDTO>() {
+             @Override
+             public int compare(BulletinDTO bulletin1, BulletinDTO bulletin2) {
+                 if (bulletin1 == null && bulletin2 == null) {
+                     return 0;
+                 } else if (bulletin1 == null) {
+                     return 1;
+                 } else if (bulletin2 == null) {
+                     return -1;
+                 }
+ 
+                 final Date timestamp1 = bulletin1.getTimestamp();
+                 final Date timestamp2 = bulletin2.getTimestamp();
+                 if (timestamp1 == null && timestamp2 == null) {
+                     return 0;
+                 } else if (timestamp1 == null) {
+                     return 1;
+                 } else if (timestamp2 == null) {
+                     return -1;
+                 } else {
+                     return timestamp1.compareTo(timestamp2);
+                 }
+             }
+         });
+ 
+         // create the bulletin board
+         final BulletinBoardDTO bulletinBoard = new BulletinBoardDTO();
+         bulletinBoard.setBulletins(bulletins);
+         bulletinBoard.setGenerated(new Date());
+         return bulletinBoard;
+     }
+ 
+     /**
+      * Creates a BulletinDTO for the specified Bulletin.
+      *
+      * @param bulletin
+      * @return
+      */
+     public BulletinDTO createBulletinDto(final Bulletin bulletin) {
+         final BulletinDTO dto = new BulletinDTO();
+         dto.setId(bulletin.getId());
+         dto.setNodeAddress(bulletin.getNodeAddress());
+         dto.setTimestamp(bulletin.getTimestamp());
+         dto.setGroupId(bulletin.getGroupId());
+         dto.setSourceId(bulletin.getSourceId());
+         dto.setSourceName(bulletin.getSourceName());
+         dto.setCategory(bulletin.getCategory());
+         dto.setLevel(bulletin.getLevel());
+         dto.setMessage(bulletin.getMessage());
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProvenanceEventNodeDTO for the specified
+      * ProvenanceEventLineageNode.
+      *
+      * @param node
+      * @return
+      */
+     public ProvenanceNodeDTO createProvenanceEventNodeDTO(final ProvenanceEventLineageNode node) {
+         final ProvenanceNodeDTO dto = new ProvenanceNodeDTO();
+         dto.setId(node.getIdentifier());
+         dto.setType("EVENT");
+         dto.setEventType(node.getEventType().toString());
+         dto.setTimestamp(new Date(node.getTimestamp()));
+         dto.setMillis(node.getTimestamp());
+         dto.setFlowFileUuid(node.getFlowFileUuid());
+         dto.setParentUuids(node.getParentUuids());
+         dto.setChildUuids(node.getChildUuids());
+         dto.setClusterNodeIdentifier(node.getClusterNodeIdentifier());
+         return dto;
+     }
+ 
+     /**
+      * Creates a FlowFileNodeDTO for the specified LineageNode.
+      *
+      * @param node
+      * @return
+      */
+     public ProvenanceNodeDTO createFlowFileNodeDTO(final LineageNode node) {
+         final ProvenanceNodeDTO dto = new ProvenanceNodeDTO();
+         dto.setId(node.getIdentifier());
+         dto.setType("FLOWFILE");
+         dto.setTimestamp(new Date(node.getTimestamp()));
+         dto.setMillis(node.getTimestamp());
+         dto.setFlowFileUuid(node.getFlowFileUuid());
+         dto.setClusterNodeIdentifier(node.getClusterNodeIdentifier());
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProvenanceLinkDTO for the specified LineageEdge.
+      *
+      * @param edge
+      * @return
+      */
+     public ProvenanceLinkDTO createProvenanceLinkDTO(final LineageEdge edge) {
+         final LineageNode source = edge.getSource();
+         final LineageNode target = edge.getDestination();
+ 
+         final ProvenanceLinkDTO dto = new ProvenanceLinkDTO();
+         dto.setTimestamp(new Date(target.getTimestamp()));
+         dto.setMillis(target.getTimestamp());
+         dto.setFlowFileUuid(edge.getUuid());
+         dto.setSourceId(source.getIdentifier());
+         dto.setTargetId(target.getIdentifier());
+         return dto;
+     }
+ 
+     /**
+      * Creates a LineageDTO for the specified Lineage.
+      *
+      * @param computeLineageSubmission
+      * @return
+      */
+     public LineageDTO createLineageDto(final ComputeLineageSubmission computeLineageSubmission) {
+         // build the lineage dto
+         final LineageDTO dto = new LineageDTO();
+         final LineageRequestDTO requestDto = new LineageRequestDTO();
+         final LineageResultsDTO resultsDto = new LineageResultsDTO();
+ 
+         // include the original request and results
+         dto.setRequest(requestDto);
+         dto.setResults(resultsDto);
+ 
+         // rebuild the request from the submission object
+         switch (computeLineageSubmission.getLineageComputationType()) {
+             case EXPAND_CHILDREN:
+                 requestDto.setEventId(computeLineageSubmission.getExpandedEventId());
+                 requestDto.setLineageRequestType(LineageRequestType.CHILDREN);
+                 break;
+             case EXPAND_PARENTS:
+                 requestDto.setEventId(computeLineageSubmission.getExpandedEventId());
+                 requestDto.setLineageRequestType(LineageRequestType.PARENTS);
+                 break;
+             case FLOWFILE_LINEAGE:
+                 final Collection<String> uuids = computeLineageSubmission.getLineageFlowFileUuids();
+                 if (uuids.size() == 1) {
+                     requestDto.setUuid(uuids.iterator().next());
+                 }
+                 requestDto.setLineageRequestType(LineageRequestType.FLOWFILE);
+                 break;
+         }
+ 
+         // include lineage details
+         dto.setId(computeLineageSubmission.getLineageIdentifier());
+         dto.setSubmissionTime(computeLineageSubmission.getSubmissionTime());
+ 
+         // create the results dto
+         final ComputeLineageResult results = computeLineageSubmission.getResult();
+         dto.setFinished(results.isFinished());
+         dto.setPercentCompleted(results.getPercentComplete());
+         dto.setExpiration(results.getExpiration());
+ 
+         final List<LineageNode> nodes = results.getNodes();
+         final List<LineageEdge> edges = results.getEdges();
+ 
+         final List<ProvenanceNodeDTO> nodeDtos = new ArrayList<>();
+         if (results.isFinished()) {
+             // create the node dto's
+             for (final LineageNode node : nodes) {
+                 switch (node.getNodeType()) {
+                     case FLOWFILE_NODE:
+                         nodeDtos.add(createFlowFileNodeDTO(node));
+                         break;
+                     case PROVENANCE_EVENT_NODE:
+                         nodeDtos.add(createProvenanceEventNodeDTO((ProvenanceEventLineageNode) node));
+                         break;
+                 }
+             }
+         }
+         resultsDto.setNodes(nodeDtos);
+ 
+         // include any errors
+         if (results.getError() != null) {
+             final Set<String> errors = new HashSet<>();
+             errors.add(results.getError());
+             resultsDto.setErrors(errors);
+         }
+ 
+         // create the link dto's
+         final List<ProvenanceLinkDTO> linkDtos = new ArrayList<>();
+         for (final LineageEdge edge : edges) {
+             linkDtos.add(createProvenanceLinkDTO(edge));
+         }
+         resultsDto.setLinks(linkDtos);
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a SystemDiagnosticsDTO for the specified SystemDiagnostics.
+      *
+      * @param sysDiagnostics
+      * @return
+      */
+     public SystemDiagnosticsDTO createSystemDiagnosticsDto(final SystemDiagnostics sysDiagnostics) {
+ 
+         final SystemDiagnosticsDTO dto = new SystemDiagnosticsDTO();
+         dto.setStatsLastRefreshed(new Date(sysDiagnostics.getCreationTimestamp()));
+ 
+         // processors
+         dto.setAvailableProcessors(sysDiagnostics.getAvailableProcessors());
+         dto.setProcessorLoadAverage(sysDiagnostics.getProcessorLoadAverage());
+ 
+         // threads
+         dto.setDaemonThreads(sysDiagnostics.getDaemonThreads());
+         dto.setTotalThreads(sysDiagnostics.getTotalThreads());
+ 
+         // heap
+         dto.setMaxHeap(FormatUtils.formatDataSize(sysDiagnostics.getMaxHeap()));
+         dto.setTotalHeap(FormatUtils.formatDataSize(sysDiagnostics.getTotalHeap()));
+         dto.setUsedHeap(FormatUtils.formatDataSize(sysDiagnostics.getUsedHeap()));
+         dto.setFreeHeap(FormatUtils.formatDataSize(sysDiagnostics.getFreeHeap()));
+         dto.setHeapUtilization(FormatUtils.formatUtilization(sysDiagnostics.getHeapUtilization()));
+ 
+         // non heap
+         dto.setMaxNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getMaxNonHeap()));
+         dto.setTotalNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getTotalNonHeap()));
+         dto.setUsedNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getUsedNonHeap()));
+         dto.setFreeNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getFreeNonHeap()));
+         dto.setNonHeapUtilization(FormatUtils.formatUtilization(sysDiagnostics.getNonHeapUtilization()));
+ 
+         // flow file disk usage
+         final SystemDiagnosticsDTO.StorageUsageDTO flowFileRepositoryStorageUsageDto = createStorageUsageDTO(null, sysDiagnostics.getFlowFileRepositoryStorageUsage());
+         dto.setFlowFileRepositoryStorageUsage(flowFileRepositoryStorageUsageDto);
+ 
+         // content disk usage
+         final Set<SystemDiagnosticsDTO.StorageUsageDTO> contentRepositoryStorageUsageDtos = new LinkedHashSet<>();
+         dto.setContentRepositoryStorageUsage(contentRepositoryStorageUsageDtos);
+         for (final Map.Entry<String, StorageUsage> entry : sysDiagnostics.getContentRepositoryStorageUsage().entrySet()) {
+             contentRepositoryStorageUsageDtos.add(createStorageUsageDTO(entry.getKey(), entry.getValue()));
+         }
+ 
+         // garbage collection
+         final Set<SystemDiagnosticsDTO.GarbageCollectionDTO> garbageCollectionDtos = new LinkedHashSet<>();
+         dto.setGarbageCollection(garbageCollectionDtos);
+         for (final Map.Entry<String, GarbageCollection> entry : sysDiagnostics.getGarbageCollection().entrySet()) {
+             garbageCollectionDtos.add(createGarbageCollectionDTO(entry.getKey(), entry.getValue()));
+         }
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a StorageUsageDTO from the specified StorageUsage.
+      *
+      * @param identifier
+      * @param storageUsage
+      * @return
+      */
+     public SystemDiagnosticsDTO.StorageUsageDTO createStorageUsageDTO(final String identifier, final StorageUsage storageUsage) {
+         final SystemDiagnosticsDTO.StorageUsageDTO dto = new SystemDiagnosticsDTO.StorageUsageDTO();
+         dto.setIdentifier(identifier);
+         dto.setFreeSpace(FormatUtils.formatDataSize(storageUsage.getFreeSpace()));
+         dto.setTotalSpace(FormatUtils.formatDataSize(storageUsage.getTotalSpace()));
+         dto.setUsedSpace(FormatUtils.formatDataSize(storageUsage.getUsedSpace()));
+         dto.setFreeSpaceBytes(storageUsage.getFreeSpace());
+         dto.setTotalSpaceBytes(storageUsage.getTotalSpace());
+         dto.setUsedSpaceBytes(storageUsage.getUsedSpace());
+         dto.setUtilization(FormatUtils.formatUtilization(storageUsage.getDiskUtilization()));
+         return dto;
+     }
+ 
+     /**
+      * Creates a GarbageCollectionDTO from the specified GarbageCollection.
+      *
+      * @param name
+      * @param garbageCollection
+      * @return
+      */
+     public SystemDiagnosticsDTO.GarbageCollectionDTO createGarbageCollectionDTO(final String name, final GarbageCollection garbageCollection) {
+         final SystemDiagnosticsDTO.GarbageCollectionDTO dto = new SystemDiagnosticsDTO.GarbageCollectionDTO();
+         dto.setName(name);
+         dto.setCollectionCount(garbageCollection.getCollectionCount());
+         dto.setCollectionTime(FormatUtils.formatHoursMinutesSeconds(garbageCollection.getCollectionTime(), TimeUnit.MILLISECONDS));
+         return dto;
+     }
+ 
+     /**
+      * Creates a ProcessorConfigDTO from the specified ProcessorNode.
+      *
+      * @param procNode
+      * @return
+      */
+     public ProcessorConfigDTO createProcessorConfigDto(final ProcessorNode procNode) {
+         if (procNode == null) {
+             return null;
+         }
+ 
+         final ProcessorConfigDTO dto = new ProcessorConfigDTO();
+ 
+         // sort a copy of the properties
+         final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+             @Override
+             public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+                 return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+             }
+         });
+         sortedProperties.putAll(procNode.getProperties());
+ 
+         // get the property order from the processor
+         final Processor processor = procNode.getProcessor();
+         final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+         final List<PropertyDescriptor> descriptors = processor.getPropertyDescriptors();
+         if (descriptors != null && !descriptors.isEmpty()) {
+             for (PropertyDescriptor descriptor : descriptors) {
+                 orderedProperties.put(descriptor, null);
+             }
+         }
+         orderedProperties.putAll(sortedProperties);
+ 
+         // build the descriptor and property dtos
+         dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+         dto.setProperties(new LinkedHashMap<String, String>());
+         for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+             final PropertyDescriptor descriptor = entry.getKey();
+ 
+             // store the property descriptor
+             dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor));
+ 
+             // determine the property value - don't include sensitive properties
+             String propertyValue = entry.getValue();
+             if (propertyValue != null && descriptor.isSensitive()) {
+                 propertyValue = "********";
+             }
+ 
+             // set the property value
+             dto.getProperties().put(descriptor.getName(), propertyValue);
+         }
+ 
+         dto.setSchedulingPeriod(procNode.getSchedulingPeriod());
+         dto.setPenaltyDuration(procNode.getPenalizationPeriod());
+         dto.setYieldDuration(procNode.getYieldPeriod());
+         dto.setRunDurationMillis(procNode.getRunDuration(TimeUnit.MILLISECONDS));
+         dto.setConcurrentlySchedulableTaskCount(procNode.getMaxConcurrentTasks());
+         dto.setLossTolerant(procNode.isLossTolerant());
+         dto.setComments(procNode.getComments());
+         dto.setBulletinLevel(procNode.getBulletinLevel().name());
+         dto.setSchedulingStrategy(procNode.getSchedulingStrategy().name());
+         dto.setAnnotationData(procNode.getAnnotationData());
+ 
+         // set up the default values for concurrent tasks and scheduling period
+         final Map<String, String> defaultConcurrentTasks = new HashMap<>();
+         defaultConcurrentTasks.put(SchedulingStrategy.TIMER_DRIVEN.name(), String.valueOf(SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks()));
+         defaultConcurrentTasks.put(SchedulingStrategy.EVENT_DRIVEN.name(), String.valueOf(SchedulingStrategy.EVENT_DRIVEN.getDefaultConcurrentTasks()));
+         defaultConcurrentTasks.put(SchedulingStrategy.CRON_DRIVEN.name(), String.valueOf(SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks()));
+         dto.setDefaultConcurrentTasks(defaultConcurrentTasks);
+ 
+         final Map<String, String> defaultSchedulingPeriod = new HashMap<>();
+         defaultSchedulingPeriod.put(SchedulingStrategy.TIMER_DRIVEN.name(), SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+         defaultSchedulingPeriod.put(SchedulingStrategy.CRON_DRIVEN.name(), SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+         dto.setDefaultSchedulingPeriod(defaultSchedulingPeriod);
+ 
+         return dto;
+     }
+ 
+     /**
+      * Creates a PropertyDesriptorDTO from the specified PropertyDesriptor.
+      *
+      * @param propertyDescriptor
+      * @return
+      */
+     private ProcessorConfigDTO.PropertyDescriptorDTO createPropertyDescriptorDto(final PropertyDescriptor propertyDescriptor) {
+         if (propertyDescriptor == null) {
+             return null;
+         }
+ 
+         final ProcessorConfigDTO.PropertyDescriptorDTO dto = new ProcessorConfigDTO.PropertyDescriptorDTO();
+ 
+         dto.setName(propertyDescriptor.getName());
+         dto.setDisplayName(propertyDescriptor.getDisplayName());
+         dto.setRequired(propertyDescriptor.isRequired());
+         dto.setSensitive(propertyDescriptor.isSensitive());
+         dto.setDynamic(propertyDescriptor.isDynamic());
+         dto.setDescription(propertyDescriptor.getDescription());
+         dto.setDefaultValue(propertyDescriptor.getDefaultValue());
+         dto.setSupportsEl(propertyDescriptor.isExpressionLanguageSupported());
+ 
+         final Class<? extends ControllerService> serviceDefinition = propertyDescriptor.getControllerServiceDefinition();
+         if (propertyDescriptor.getAllowableValues() == null) {
+             if (serviceDefinition == null) {
+                 dto.setAllowableValues(null);
+             } else {
+                 final Set<AllowableValueDTO> allowableValues = new LinkedHashSet<>();
+                 for (final String serviceIdentifier : controllerServiceLookup.getControllerServiceIdentifiers(serviceDefinition)) {
+                     String displayName = serviceIdentifier;
+ 
+                     // TODO: attempt to get the controller service name
+                     final ControllerService controllerService = controllerServiceLookup.getControllerService(serviceIdentifier);
+ 
+                     final AllowableValueDTO allowableValue = new AllowableValueDTO();
+                     allowableValue.setDisplayName(displayName);
+                     allowableValue.setValue(serviceIdentifier);
+                     allowableValues.add(allowableValue);
+                 }
+                 dto.setAllowableValues(allowableValues);
+             }
+         } else {
+             final Set<AllowableValueDTO> allowableValues = new LinkedHashSet<>();
+             for (final AllowableValue allowableValue : propertyDescriptor.getAllowableValues()) {
+                 final AllowableValueDTO allowableValueDto = new AllowableValueDTO();
+                 allowableValueDto.setDisplayName(allowableValue.getDisplayName());
+                 allowableValueDto.setValue(allowableValue.getValue());
+                 allowableValueDto.setDescription(allowableValue.getDescription());
+                 allowableValues.add(allowableValueDto);
+             }
+             dto.setAllowableValues(allowableValues);
+         }
+ 
+         return dto;
+     }
+ 
+     // 
+     // Copy methods
+     //
+     public LabelDTO copy(final LabelDTO original) {
+         final LabelDTO copy = new LabelDTO();
+         copy.setId(original.getId());
+         copy.setParentGroupId(original.getParentGroupId());
+         copy.setLabel(original.getLabel());
+         copy.setStyle(copy(original.getStyle()));
+         copy.setPosition(original.getPosition());
+         copy.setWidth(original.getWidth());
+         copy.setHeight(original.getHeight());
+         copy.setUri(original.getUri());
+ 
+         return copy;
+     }
+ 
+     public FunnelDTO copy(final FunnelDTO original) {
+         final FunnelDTO copy = new FunnelDTO();
+         copy.setId(original.getId());
+         copy.setParentGroupId(original.getParentGroupId());
+         copy.setPosition(original.getPosition());
+         copy.setUri(original.getUri());
+ 
+         return copy;
+     }
+ 
+     private <T> List<T> copy(final List<T> original) {
+         if (original == null) {
+             return null;
+         } else {
+             return new ArrayList<>(original);
+         }
+     }
+ 
+     private <T> List<T> copy(final Collection<T> original) {
+         if (original == null) {
+             return null;
+         } else {
+             return new ArrayList<>(original);
+         }
+     }
+ 
+     private <T> Set<T> copy(final Set<T> original) {
+         if (original == null) {
+             return null;
+         } else {
+             return new LinkedHashSet<>(original);
+         }
+     }
+ 
+     private <S, T> Map<S, T> copy(final Map<S, T> original) {
+         if (original == null) {
+             return null;
+         } else {
+             return new LinkedHashMap<>(original);
+         }
+     }
+ 
+     public ProcessorDTO copy(final ProcessorDTO original) {
+         final ProcessorDTO copy = new ProcessorDTO();
+         copy.setConfig(copy(original.getConfig()));
+         copy.setPosition(original.getPosition());
+         copy.setId(original.getId());
+         copy.setName(original.getName());
+         copy.setDescription(original.getDescription());
+         copy.setParentGroupId(original.getParentGroupId());
+         copy.setRelationships(copy(original.getRelationships()));
+         copy.setState(original.getState());
+         copy.setStyle(copy(original.getStyle()));
+         copy.setType(original.getType());
+         copy.setUri(original.getUri());
+         copy.setSupportsParallelProcessing(original.getSupportsParallelProcessing());
+         copy.setSupportsEventDriven(original.getSupportsEventDriven());
+         copy.setValidationErrors(copy(original.getValidationErrors()));
+ 
+         return copy;
+     }
+ 
+     private ProcessorConfigDTO copy(final ProcessorConfigDTO original) {
+         final ProcessorConfigDTO copy = new ProcessorConfigDTO();
+         copy.setAnnotationData(original.getAnnotationData());
+         copy.setAutoTerminatedRelationships(copy(original.getAutoTerminatedRelationships()));
+         copy.setComments(original.getComments());
+         copy.setSchedulingStrategy(original.getSchedulingStrategy());
+         copy.setConcurrentlySchedulableTaskCount(original.getConcurrentlySchedulableTaskCount());
+         copy.setCustomUiUrl(original.getCustomUiUrl());
+         copy.setDescriptors(copy(original.getDescriptors()));
+         copy.setProperties(copy(original.getProperties()));
+         copy.setSchedulingPeriod(original.getSchedulingPeriod());
+         copy.setPenaltyDuration(original.getPenaltyDuration());
+         copy.setYieldDuration(original.getYieldDuration());
+         copy.setRunDurationMillis(original.getRunDurationMillis());
+         copy.setBulletinLevel(original.getBulletinLevel());
+         copy.setDefaultConcurrentTasks(original.getDefaultConcurrentTasks());
+         copy.setDefaultSchedulingPeriod(original.getDefaultSchedulingPeriod());
+         copy.setLossTolerant(original.isLossTolerant());
+ 
+         return copy;
+     }
+ 
+     public ConnectionDTO copy(final ConnectionDTO original) {
+         final ConnectionDTO copy = new ConnectionDTO();
+         copy.setAvailableRelationships(copy(original.getAvailableRelationships()));
+         copy.setDestination(original.getDestination());
+         copy.setPosition(original.getPosition());
+         copy.setId(original.getId());
+         copy.setName(original.getName());
+         copy.setParentGroupId(original.getParentGroupId());
+         copy.setSelectedRelationships(copy(original.getSelectedRelationships()));
+         copy.setFlowFileExpiration(original.getFlowFileExpiration());
+         copy.setBackPressureObjectThreshold(original.getBackPressureObjectThreshold());
+         copy.setBackPressureDataSizeThreshold(original.getBackPressureDataSizeThreshold());
+         copy.setPrioritizers(copy(original.getPrioritizers()));
+         copy.setSource(original.getSource());
+         copy.setUri(original.getUri());
+         copy.setzIndex(original.getzIndex());
+         copy.setLabelIndex(original.getLabelIndex());
+         copy.setBends(copy(original.getBends()));
+ 
+         return copy;
+     }
+ 
+     public BulletinDTO copy(final BulletinDTO original) {
+         final BulletinDTO copy = new BulletinDTO();
+         copy.setId(original.getId());
+         copy.setTimestamp(original.getTimestamp());
+         copy.setGroupId(original.getGroupId());
+         copy.setSourceId(original.getSourceId());
+         copy.setSourceName(original.getSourceName());
+         copy.setCategory(original.getCategory());
+         copy.setLevel(original.getLevel());
+         copy.setMessage(original.getMessage());
+         copy.setNodeAddress(original.getNodeAddress());
+         return copy;
+     }
+ 
+     public PortDTO copy(final PortDTO original) {
+         final PortDTO copy = new PortDTO();
+         copy.setPosition(original.getPosition());
+         copy.setId(original.getId());
+         copy.setName(original.getName());
+         copy.setComments(original.getComments());
+         copy.setParentGroupId(original.getParentGroupId());
+         copy.setUri(original.getUri());
+         copy.setState(original.getState());
+         copy.setType(original.getType());
+         copy.setTransmitting(original.isTransmitting());
+         copy.setConcurrentlySchedulableTaskCount(original.getConcurrentlySchedulableTaskCount());
+         copy.setUserAccessControl(copy(original.getUserAccessControl()));
+         copy.setGroupAccessControl(copy(original.getGroupAccessControl()));
+         copy.setValidationErrors(copy(original.getValidationErrors()));
+         return copy;
+     }
+ 
+     public RemoteProcessGroupPortDTO copy(final RemoteProcessGroupPortDTO original) {
+         final RemoteProcessGroupPortDTO copy = new RemoteProcessGroupPortDTO();
+         copy.setId(original.getId());
+         copy.setGroupId(original.getGroupId());
+         copy.setName(original.getName());
+         copy.setComments(original.getComments());
+         copy.setConnected(original.isConnected());
+         copy.setTargetRunning(original.isTargetRunning());
+         copy.setTransmitting(original.isTransmitting());
+         copy.setConcurrentlySchedulableTaskCount(original.getConcurrentlySchedulableTaskCount());
+         copy.setUseCompression(original.getUseCompression());
+         copy.setExists(original.getExists());
+         return copy;
+     }
+ 
+     public ProcessGroupDTO copy(final ProcessGroupDTO original, final boolean deep) {
+         final ProcessGroupDTO copy = new ProcessGroupDTO();
+         copy.setComments(original.getComments());
+         copy.setContents(copy(original.getContents(), deep));
+         copy.setPosition(original.getPosition());
+         copy.setId(original.getId());
+         copy.setInputPortCount(original.getInputPortCount());
+         copy.setInvalidCount(original.getInvalidCount());
+         copy.setName(original.getName());
+         copy.setOutputPortCount(original.getOutputPortCount());
+         copy.setParent(original.getParent());
+         copy.setParentGroupId(original.getParentGroupId());
+ 
+         copy.setRunning(original.isRunning());
+         copy.setRunningCount(original.getRunningCount());
+         copy.setStoppedCount(original.getStoppedCount());
+         copy.setDisabledCount(original.getDisabledCount());
+         copy.setActiveRemotePortCount(original.getActiveRemotePortCount());
+         copy.setInactiveRemotePortCount(original.getInactiveRemotePortCount());
+         copy.setUri(original.getUri());
+ 
+         return copy;
+     }
+ 
+     public RemoteProcessGroupDTO copy(final RemoteProcessGroupDTO original) {
+         final RemoteProcessGroupContentsDTO originalContents = original.getContents();
+         final RemoteProcessGroupContentsDTO copyContents = new RemoteProcessGroupContentsDTO();
+ 
+         if (originalContents.getInputPorts() != null) {
+             final Set<RemoteProcessGroupPortDTO> inputPorts = new HashSet<>();
+             for (final RemoteProcessGroupPortDTO port : originalContents.getInputPorts()) {
+                 inputPorts.add(copy(port));
+             }
+             copyContents.setInputPorts(inputPorts);
+         }
+ 
+         if (originalContents.getOutputPorts() != null) {
+             final Set<RemoteProcessGroupPortDTO> outputPorts = new HashSet<>();
+             for (final RemoteProcessGroupPortDTO port : originalContents.getOutputPorts()) {
+                 outputPorts.add(copy(port));
+             }
+             copyContents.setOutputPorts(outputPorts);
+         }
+ 
+         final RemoteProcessGroupDTO copy = new RemoteProcessGroupDTO();
+         copy.setComments(original.getComments());
+         copy.setPosition(original.getPosition());
+         copy.setId(original.getId());
+         copy.setCommunicationsTimeout(original.getCommunicationsTimeout());
+         copy.setYieldDuration(original.getYieldDuration());
+         copy.setName(original.getName());
+         copy.setInputPortCount(original.getInputPortCount());
+         copy.setOutputPortCount(original.getOutputPortCount());
+         copy.setActiveRemoteInputPortCount(original.getActiveRemoteInputPortCount());
+         copy.setInactiveRemoteInputPortCount(original.getInactiveRemoteInputPortCount());
+         copy.setActiveRemoteOutputPortCount(original.getActiveRemoteOutputPortCount());
+         copy.setInactiveRemoteOutputPortCount(original.getInactiveRemoteOutputPortCount());
+         copy.setParentGroupId(original.getParentGroupId());
+         copy.setTargetUri(original.getTargetUri());
+         copy.setUri(original.getUri());
+ 
+         copy.setContents(copyContents);
+ 
+         return copy;
+     }
+ 
+     public Connect

<TRUNCATED>