diff --git a/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/ComponentIdGenerator.java b/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/ComponentIdGenerator.java new file mode 100644 index 0000000000..49f00b8d51 --- /dev/null +++ b/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/ComponentIdGenerator.java @@ -0,0 +1,102 @@ +/* + * 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.util; + +import java.security.SecureRandom; +import java.util.UUID; + +/** + * IMPORTANT: This component is not part of public API! + * ==================================================== + *

+ * This component generates type-one UUID. It is used for generating ID of all + * NiFi components. Giving the 128-bit UUID structure which consists of Least + * Significant Bits (LSB) and Most Significant Bits (MSB) this component + * provides support for generating and maintaining INCEPTION ID of the component + * (MSB) as well as its INSTANCE ID (LSB). + *

+ *

+ * It is also important to understand that while this component does seed itself + * from current time which could be extracted from the resulting UUID via call + * to {@link UUID#timestamp()} operation, one should not be relying on such time + * as the exact time when such ID was generated since in the event the same time + * is passed to one of the {@link #generateId()} operation it will be + * incremented by 1 since the goal of this component to only ensure uniqueness + * and type-one semantics where each UUID generated by this component is + * comparable and each subsequent ID is > then previous ID. + *

+ *

+ * For more details on how it is interacted with please see + * org.apache.nifi.web.util.SnippetUtils as well as + * org.apache.nifi.web.util.SnippetUtilsTest which contain additional + * documentation on its usage as well as ID generation contracts defined in + * NiFi. + *

+ */ +public class ComponentIdGenerator { + + public static final Object lock = new Object(); + + private static long lastTime; + private static long clockSequence = 0; + private static final SecureRandom randomGenerator = new SecureRandom(); + + /** + * Will generate unique time based UUID where the next UUID is always + * greater then the previous. + */ + public final static UUID generateId() { + return generateId(System.currentTimeMillis()); + } + + /** + * + */ + public final static UUID generateId(long currentTime) { + return generateId(currentTime, randomGenerator.nextLong(), true); + } + + /** + * + */ + public final static UUID generateId(long msb, long lsb, boolean ensureUnique) { + long time; + + synchronized (lock) { + if (ensureUnique && msb <= lastTime) { + msb = ++lastTime; + } + lastTime = msb; + } + + time = msb; + + // low Time + time = msb << 32; + + // mid Time + time |= ((msb & 0xFFFF00000000L) >> 16); + + // hi Time + time |= 0x1000 | ((msb >> 48) & 0x0FFF); + + long clockSequenceHi = clockSequence; + clockSequenceHi <<= 48; + lsb = clockSequenceHi | lsb; + return new UUID(time, lsb); + } +} \ No newline at end of file diff --git a/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/TypeOneUUIDGenerator.java b/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/TypeOneUUIDGenerator.java deleted file mode 100644 index 5d8ee22932..0000000000 --- a/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/TypeOneUUIDGenerator.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.util; - -import java.util.Random; -import java.util.UUID; - -public class TypeOneUUIDGenerator { - - public static final Object lock = new Object(); - - private static long lastTime; - private static long clockSequence = 0; - private static final Random randomGenerator = new Random(); - - /** - * Will generate unique time based UUID where the next UUID is always - * greater then the previous. - */ - public final static UUID generateId() { - return generateId(System.currentTimeMillis()); - } - - /** - * - */ - public final static UUID generateId(long currentTime) { - return generateId(currentTime, Math.abs(randomGenerator.nextInt())); - } - - /** - * - */ - public final static UUID generateId(long currentTime, int lsbInt) { - long time; - - synchronized (lock) { - if (currentTime == lastTime) { - ++clockSequence; - } else { - lastTime = currentTime; - clockSequence = 0; - } - } - - time = currentTime; - - // low Time - time = currentTime << 32; - - // mid Time - time |= ((currentTime & 0xFFFF00000000L) >> 16); - - // hi Time - time |= 0x1000 | ((currentTime >> 48) & 0x0FFF); - - long clockSequenceHi = clockSequence; - clockSequenceHi <<= 48; - long lsb = clockSequenceHi | lsbInt; - return new UUID(time, lsb); - } -} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDTO.java index f85adf5731..dfbca3d638 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDTO.java @@ -93,4 +93,9 @@ public class ComponentDTO { return id.equals(((ComponentDTO) obj).getId()); } + + @Override + public String toString() { + return this.getClass().getSimpleName() + ":" + this.getId(); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowSnippetDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowSnippetDTO.java index 63194a372f..f2681345e4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowSnippetDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowSnippetDTO.java @@ -203,19 +203,87 @@ public class FlowSnippetDTO { UUID id = UUID.fromString(componentDto.getId()); id = new UUID(id.getMostSignificantBits(), 0); componentDto.setId(id.toString()); + + id = UUID.fromString(componentDto.getParentGroupId()); + id = new UUID(id.getMostSignificantBits(), 0); + componentDto.setParentGroupId(id.toString()); + if (componentDto instanceof ConnectionDTO) { ConnectionDTO connectionDTO = (ConnectionDTO) componentDto; + ConnectableDTO cdto = connectionDTO.getSource(); id = UUID.fromString(cdto.getId()); id = new UUID(id.getMostSignificantBits(), 0); cdto.setId(id.toString()); + id = UUID.fromString(cdto.getGroupId()); + id = new UUID(id.getMostSignificantBits(), 0); + cdto.setGroupId(id.toString()); + cdto = connectionDTO.getDestination(); id = UUID.fromString(cdto.getId()); id = new UUID(id.getMostSignificantBits(), 0); cdto.setId(id.toString()); + + id = UUID.fromString(cdto.getGroupId()); + id = new UUID(id.getMostSignificantBits(), 0); + cdto.setGroupId(id.toString()); + } + if (componentDto instanceof ProcessGroupDTO) { + FlowSnippetDTO fsDTO = ((ProcessGroupDTO) componentDto).getContents(); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getConnections()); + fsDTO.connections = this.orderedById(fsDTO.getConnections()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getControllerServices()); + fsDTO.controllerServices = this.orderedById(fsDTO.getControllerServices()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getFunnels()); + fsDTO.funnels = this.orderedById(fsDTO.getFunnels()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getInputPorts()); + fsDTO.inputPorts = this.orderedById(fsDTO.getInputPorts()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getLabels()); + fsDTO.labels = this.orderedById(fsDTO.getLabels()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getOutputPorts()); + fsDTO.outputPorts = this.orderedById(fsDTO.getOutputPorts()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getProcessGroups()); + fsDTO.processGroups = this.orderedById(fsDTO.getProcessGroups()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getProcessors()); + fsDTO.processors = this.orderedById(fsDTO.getProcessors()); + + this.removeInstanceIdentifierIfNecessary(fsDTO.getRemoteProcessGroups()); + fsDTO.remoteProcessGroups = this.orderedById(fsDTO.getRemoteProcessGroups()); + } else if (componentDto instanceof RemoteProcessGroupDTO) { + RemoteProcessGroupContentsDTO contentsDTO = ((RemoteProcessGroupDTO) componentDto).getContents(); + for (RemoteProcessGroupPortDTO portDTO : contentsDTO.getInputPorts()) { + id = UUID.fromString(portDTO.getId()); + id = new UUID(id.getMostSignificantBits(), 0); + portDTO.setId(new UUID(id.getMostSignificantBits(), 0).toString()); + } + for (RemoteProcessGroupPortDTO portDTO : contentsDTO.getOutputPorts()) { + id = UUID.fromString(portDTO.getId()); + portDTO.setId(new UUID(id.getMostSignificantBits(), 0).toString()); + } + contentsDTO.setInputPorts(this.orderedRemotePortsById(contentsDTO.getInputPorts())); + contentsDTO.setOutputPorts(this.orderedRemotePortsById(contentsDTO.getOutputPorts())); } } } } + + private Set orderedRemotePortsById(Set dtos) { + TreeSet components = new TreeSet<>(new Comparator() { + @Override + public int compare(RemoteProcessGroupPortDTO c1, RemoteProcessGroupPortDTO c2) { + return UUID.fromString(c1.getId()).compareTo(UUID.fromString(c2.getId())); + } + }); + components.addAll(dtos); + return components; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java index c5a8af54a5..f4d461179f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java @@ -41,7 +41,7 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier; import org.apache.nifi.events.EventReporter; import org.apache.nifi.reporting.Severity; import org.apache.nifi.util.FormatUtils; -import org.apache.nifi.util.TypeOneUUIDGenerator; +import org.apache.nifi.util.ComponentIdGenerator; import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -219,7 +219,7 @@ public class ThreadPoolRequestReplicator implements RequestReplicator { final boolean indicateReplicated, final boolean performVerification) { final Map updatedHeaders = new HashMap<>(headers); - updatedHeaders.put(RequestReplicator.CLUSTER_ID_GENERATION_SEED_HEADER, TypeOneUUIDGenerator.generateId().toString()); + updatedHeaders.put(RequestReplicator.CLUSTER_ID_GENERATION_SEED_HEADER, ComponentIdGenerator.generateId().toString()); if (indicateReplicated) { updatedHeaders.put(RequestReplicator.REPLICATION_INDICATOR_HEADER, "true"); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java index 1b14cf8ccd..8879726335 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java @@ -186,6 +186,7 @@ import org.apache.nifi.stream.io.StreamUtils; import org.apache.nifi.util.FormatUtils; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.ReflectionUtils; +import org.apache.nifi.util.ComponentIdGenerator; import org.apache.nifi.web.ResourceNotFoundException; import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; @@ -515,9 +516,10 @@ public class FlowController implements EventAccess, ControllerServiceProvider, R this.snippetManager = new SnippetManager(); - rootGroup = new StandardProcessGroup(UUID.randomUUID().toString(), this, processScheduler, properties, encryptor, this, this.variableRegistry); + rootGroup = new StandardProcessGroup(ComponentIdGenerator.generateId().toString(), this, processScheduler, + properties, encryptor, this, this.variableRegistry); rootGroup.setName(DEFAULT_ROOT_GROUP_NAME); - instanceId = UUID.randomUUID().toString(); + instanceId = ComponentIdGenerator.generateId().toString(); controllerServiceProvider = new StandardControllerServiceProvider(this, processScheduler, bulletinRepository, stateManagerProvider, this.variableRegistry); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java index a5c4679637..21dba95957 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -43,9 +42,7 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO; import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.PropertyDescriptorDTO; import org.apache.nifi.web.api.dto.RelationshipDTO; -import org.apache.nifi.web.api.dto.RemoteProcessGroupContentsDTO; import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO; -import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO; import org.apache.nifi.web.api.dto.TemplateDTO; import org.w3c.dom.Element; @@ -282,45 +279,6 @@ public class TemplateUtils { remoteProcessGroupDTO.setName(null); remoteProcessGroupDTO.setTargetSecure(null); remoteProcessGroupDTO.setTransmitting(null); - - // if this remote process group has contents - if (remoteProcessGroupDTO.getContents() != null) { - RemoteProcessGroupContentsDTO contents = remoteProcessGroupDTO.getContents(); - - // scrub any remote input ports - if (contents.getInputPorts() != null) { - scrubRemotePorts(contents.getInputPorts()); - } - - // scrub and remote output ports - if (contents.getOutputPorts() != null) { - scrubRemotePorts(contents.getOutputPorts()); - } - } - } - } - - /** - * Remove unnecessary fields in remote ports prior to saving. - * - * @param remotePorts ports - */ - private static void scrubRemotePorts(final Set remotePorts) { - for (final Iterator remotePortIter = remotePorts.iterator(); remotePortIter.hasNext();) { - final RemoteProcessGroupPortDTO remotePortDTO = remotePortIter.next(); - - // if the flow is not connected to this remote port, remove it - if (remotePortDTO.isConnected() == null || !remotePortDTO.isConnected().booleanValue()) { - remotePortIter.remove(); - continue; - } - - remotePortDTO.setExists(null); - remotePortDTO.setTargetRunning(null); - remotePortDTO.setConnected(null); - remotePortDTO.setExists(null); - remotePortDTO.setTargetRunning(null); - remotePortDTO.setTransmitting(null); } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/persistence/TemplateSerializerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/persistence/TemplateSerializerTest.java index e5acdfea5c..8ddfdfec3a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/persistence/TemplateSerializerTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/persistence/TemplateSerializerTest.java @@ -33,7 +33,7 @@ import javax.xml.bind.JAXBElement; import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource; -import org.apache.nifi.util.TypeOneUUIDGenerator; +import org.apache.nifi.util.ComponentIdGenerator; import org.apache.nifi.web.api.dto.FlowSnippetDTO; import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.TemplateDTO; @@ -55,7 +55,7 @@ public class TemplateSerializerTest { for (int i = 4; i > 0; i--) { ProcessorDTO procDTO = new ProcessorDTO(); procDTO.setType("Processor" + i + ".class"); - procDTO.setId(TypeOneUUIDGenerator.generateId().toString()); + procDTO.setId(ComponentIdGenerator.generateId().toString()); procs.add(procDTO); } snippet.setProcessors(procs); @@ -86,7 +86,7 @@ public class TemplateSerializerTest { // add new Processor ProcessorDTO procDTO = new ProcessorDTO(); procDTO.setType("ProcessorNew" + ".class"); - procDTO.setId(TypeOneUUIDGenerator.generateId().toString()); + procDTO.setId(ComponentIdGenerator.generateId().toString()); deserProcs.add(procDTO); // Serialize modified template diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java index 6a9e1d08f8..2c5b43eb78 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java @@ -41,7 +41,7 @@ import org.apache.nifi.remote.exception.NotAuthorizedException; import org.apache.nifi.remote.protocol.ResponseCode; import org.apache.nifi.remote.protocol.http.HttpHeaders; import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.util.TypeOneUUIDGenerator; +import org.apache.nifi.util.ComponentIdGenerator; import org.apache.nifi.authorization.AuthorizableLookup; import org.apache.nifi.authorization.AuthorizeAccess; import org.apache.nifi.web.NiFiServiceFacade; @@ -205,14 +205,15 @@ public abstract class ApplicationResource { if (seed.isPresent()) { try { UUID seedId = UUID.fromString(seed.get()); - uuid = new UUID(seedId.getMostSignificantBits(), Math.abs(seed.get().hashCode())); + uuid = new UUID(seedId.getMostSignificantBits(), seed.get().hashCode()); } catch (Exception e) { logger.warn("Provided 'seed' does not represent UUID. Will not be able to extract most significant bits for ID generation."); uuid = UUID.nameUUIDFromBytes(seed.get().getBytes(StandardCharsets.UTF_8)); } } else { - uuid = TypeOneUUIDGenerator.generateId(); + uuid = ComponentIdGenerator.generateId(); } + return uuid.toString(); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java index f88889e699..18aeca851d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java @@ -16,6 +16,22 @@ */ package org.apache.nifi.web.util; + + +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.AccessPolicy; import org.apache.nifi.authorization.RequestAction; @@ -36,7 +52,7 @@ import org.apache.nifi.controller.service.ControllerServiceNode; import org.apache.nifi.controller.service.ControllerServiceState; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.groups.RemoteProcessGroup; -import org.apache.nifi.util.TypeOneUUIDGenerator; +import org.apache.nifi.util.ComponentIdGenerator; import org.apache.nifi.web.api.dto.AccessPolicyDTO; import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; @@ -58,19 +74,6 @@ import org.apache.nifi.web.dao.AccessPolicyDAO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - /** * Template utilities. */ @@ -78,6 +81,8 @@ public final class SnippetUtils { private static final Logger logger = LoggerFactory.getLogger(SnippetUtils.class); + private static final SecureRandom randomGenerator = new SecureRandom(); + private FlowController flowController; private DtoFactory dtoFactory; private AccessPolicyDAO accessPolicyDAO; @@ -771,15 +776,44 @@ public final class SnippetUtils { } /** - * Generates a new id for the current id that is specified. If no seed is found, a new random id will be created. + * Generates a new type 1 id (UUID) for the current id that is specified. If + * seed is provided, it will be incorporated into generation logic of the + * new ID. + * The contract of this method is as follows: + * - The 'currentId' must never be null and it must be String representation + * of type-one UUID. + * - If seed is provided, the new ID will be generated from the 'msb' extracted from + * the 'currentId' and the 'lsb' extracted from the UUID generated via + * UUID.nameUUIDFromBytes(currentId + seed). + * - If seed is NOT provided and 'isCopy' flag is set the new ID will be generated from + * the 'msb' extracted from the 'currentId' and random integer as 'lsb'. In this case + * the new ID will always be > the previous ID essentially resulting in the new ID for + * the component that being copied (e.g., copy/paste). + * - If seed is NOT provided and 'isCopy' flag is NOT set the new ID will be generated from + * the 'msb' extracted from the 'currentId' and random integer as 'lsb'. */ private String generateId(final String currentId, final String seed, boolean isCopy) { long msb = UUID.fromString(currentId).getMostSignificantBits(); - int lsb = StringUtils.isBlank(seed) - ? Math.abs(new Random().nextInt()) - : Math.abs(seed.hashCode()); - return isCopy ? TypeOneUUIDGenerator.generateId(msb, lsb).toString() : new UUID(msb, lsb).toString(); + UUID uuid; + if (StringUtils.isBlank(seed)) { + long lsb = randomGenerator.nextLong(); + if (isCopy) { + uuid = ComponentIdGenerator.generateId(msb, lsb, true); // will increment msb if necessary + } else { + // since msb is extracted from type-one UUID, the type-one semantics will be preserved + uuid = new UUID(msb, lsb); + } + } else { + UUID seedId = UUID.nameUUIDFromBytes((currentId + seed).getBytes(StandardCharsets.UTF_8)); + if (isCopy) { + // will ensure the type-one semantics for new UUID generated from msb extracted from seedId + uuid = ComponentIdGenerator.generateId(seedId.getMostSignificantBits(), seedId.getLeastSignificantBits(), false); + } else { + uuid = new UUID(msb, seedId.getLeastSignificantBits()); + } + } + return uuid.toString(); } /* setters */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/util/SnippetUtilsTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/util/SnippetUtilsTest.java new file mode 100644 index 0000000000..0c169b057b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/util/SnippetUtilsTest.java @@ -0,0 +1,219 @@ +/* + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.apache.nifi.util.ComponentIdGenerator; +import org.junit.Test; + +public class SnippetUtilsTest { + + /* + * This test validates condition where component is being replicated across + * the cluster + */ + @Test + public void validateWithSameSeedSameInceptionIdSameInstanceId() throws Exception { + Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class, + boolean.class); + generateIdMethod.setAccessible(true); + + SnippetUtils utils = new SnippetUtils(); + String currentId = ComponentIdGenerator.generateId().toString(); + String seed = ComponentIdGenerator.generateId().toString(); + String id1 = (String) generateIdMethod.invoke(utils, currentId, seed, true); + String id2 = (String) generateIdMethod.invoke(utils, currentId, seed, true); + String id3 = (String) generateIdMethod.invoke(utils, currentId, seed, true); + assertEquals(id1, id2); + assertEquals(id2, id3); + } + + /* + * This test validates condition where components that are being copy/pasted from + * one another are now replicated across the cluster. Such components will + * have different inception id (msb) yet different instance id (lsb). The id + * of these components must be different yet their msb must be the same. + */ + @Test + public void validateWithSameSeedSameInceptionIdNotSameInstanceIdIsCopySet() throws Exception { + Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class, + boolean.class); + generateIdMethod.setAccessible(true); + + SnippetUtils utils = new SnippetUtils(); + String seed = ComponentIdGenerator.generateId().toString(); + + UUID rootId = ComponentIdGenerator.generateId(); + + String id1 = (String) generateIdMethod.invoke(utils, + new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(), + seed, true); + String id2 = (String) generateIdMethod.invoke(utils, + new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(), + seed, true); + String id3 = (String) generateIdMethod.invoke(utils, + new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(), + seed, true); + assertNotEquals(id1, id2); + assertNotEquals(id2, id3); + UUID uuid1 = UUID.fromString(id1); + UUID uuid2 = UUID.fromString(id2); + UUID uuid3 = UUID.fromString(id3); + // below simply validates that generated UUID is type-one, since timestamp() operation will result + // in exception if generated UUID is not type-one + uuid1.timestamp(); + uuid2.timestamp(); + uuid3.timestamp(); + assertNotEquals(uuid1.getMostSignificantBits(), uuid2.getMostSignificantBits()); + assertNotEquals(uuid2.getMostSignificantBits(), uuid3.getMostSignificantBits()); + } + + /* + * This test validates condition where components that are being re-created + * from template are now replicated across the cluster. Such components will + * have the same inception id (msb) yet different instance id (lsb). The id + * of these components must be different yet their msb must be the same. + */ + @Test + public void validateWithSameSeedSameInceptionIdNotSameInstanceIdIsCopyNotSet() throws Exception { + Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class, + boolean.class); + generateIdMethod.setAccessible(true); + + SnippetUtils utils = new SnippetUtils(); + String seed = ComponentIdGenerator.generateId().toString(); + + UUID rootId = ComponentIdGenerator.generateId(); + + String id1 = (String) generateIdMethod.invoke(utils, + new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(), + seed, false); + String id2 = (String) generateIdMethod.invoke(utils, + new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(), + seed, false); + String id3 = (String) generateIdMethod.invoke(utils, + new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(), + seed, false); + assertNotEquals(id1, id2); + assertNotEquals(id2, id3); + UUID uuid1 = UUID.fromString(id1); + UUID uuid2 = UUID.fromString(id2); + UUID uuid3 = UUID.fromString(id3); + // below simply validates that generated UUID is type-one, since timestamp() operation will result + // in exception if generated UUID is not type-one + uuid1.timestamp(); + uuid2.timestamp(); + uuid3.timestamp(); + assertEquals(uuid1.getMostSignificantBits(), uuid2.getMostSignificantBits()); + assertEquals(uuid2.getMostSignificantBits(), uuid3.getMostSignificantBits()); + } + + /* + * This test validates condition where components are being copied from one + * another. The ids of each components must be completely different (msb and + * lsb) yet each subsequent msb must be > then previous component's msb. + */ + @Test + public void validateWithoutSeedSameCurrentIdIsCopySet() throws Exception { + Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class, + boolean.class); + generateIdMethod.setAccessible(true); + + boolean isCopy = true; + + SnippetUtils utils = new SnippetUtils(); + String currentId = ComponentIdGenerator.generateId().toString(); + UUID id1 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy)); + UUID id2 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy)); + UUID id3 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy)); + // below simply validates that generated UUID is type-one, since timestamp() operation will result + // in exception if generated UUID is not type-one + id1.timestamp(); + id2.timestamp(); + id3.timestamp(); + assertTrue(id1.getMostSignificantBits() < id2.getMostSignificantBits()); + assertTrue(id2.getMostSignificantBits() < id3.getMostSignificantBits()); + } + + /* + * This test validates condition where new components are being created from + * existing components such as from imported templates. In this case their + * instance id (lsb) is irrelevant and new instance id would have to be + * generated every time yet its inception id (msb) must remain the same. + */ + @Test + public void validateWithoutSeedSameCurrentIdIsCopyNotSet() throws Exception { + Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class, + boolean.class); + generateIdMethod.setAccessible(true); + + boolean isCopy = false; + + SnippetUtils utils = new SnippetUtils(); + String currentId = ComponentIdGenerator.generateId().toString(); + UUID id1 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy)); + UUID id2 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy)); + UUID id3 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy)); + // below simply validates that generated UUID is type-one, since timestamp() operation will result + // in exception if generated UUID is not type-one + id1.timestamp(); + id2.timestamp(); + id3.timestamp(); + assertEquals(id1.getMostSignificantBits(), id2.getMostSignificantBits()); + assertEquals(id2.getMostSignificantBits(), id3.getMostSignificantBits()); + } + + /* + * This test simply validates that generated IDs are comparable and + * sequentially correct where each subsequent ID is > previous ID. + */ + @Test + public void validateIdOrdering() throws Exception { + UUID seed = ComponentIdGenerator.generateId(); + UUID currentId1 = ComponentIdGenerator.generateId(); + UUID currentId2 = ComponentIdGenerator.generateId(); + UUID currentId3 = ComponentIdGenerator.generateId(); + + UUID id1 = new UUID(currentId1.getMostSignificantBits(), + UUID.nameUUIDFromBytes((currentId1.toString() + seed.toString()).getBytes(StandardCharsets.UTF_8)) + .getLeastSignificantBits()); + UUID id2 = new UUID(currentId2.getMostSignificantBits(), + UUID.nameUUIDFromBytes((currentId2.toString() + seed.toString()).getBytes(StandardCharsets.UTF_8)) + .getLeastSignificantBits()); + UUID id3 = new UUID(currentId3.getMostSignificantBits(), + UUID.nameUUIDFromBytes((currentId3.toString() + seed.toString()).getBytes(StandardCharsets.UTF_8)) + .getLeastSignificantBits()); + List list = new ArrayList<>(); + list.add(id2); + list.add(id3); + list.add(id1); + Collections.sort(list); + assertEquals(id1, list.get(0)); + assertEquals(id2, list.get(1)); + assertEquals(id3, list.get(2)); + } +}