Skip to content

Commit

Permalink
Merge HashPipe and CheckSumPipe and add hashEncoding (#7456)
Browse files Browse the repository at this point in the history
  • Loading branch information
evandongen authored Sep 11, 2024
1 parent 5b88304 commit 40970b7
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 344 deletions.
180 changes: 50 additions & 130 deletions core/src/main/java/org/frankframework/pipes/ChecksumPipe.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2013, 2020 Nationale-Nederlanden, 2020, 2022 WeAreFrank!
Copyright 2013, 2020 Nationale-Nederlanden, 2020-2024 WeAreFrank!
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -16,163 +16,74 @@
package org.frankframework.pipes;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

import lombok.Getter;

import org.frankframework.configuration.ConfigurationException;
import org.frankframework.configuration.ConfigurationWarning;
import org.frankframework.core.PipeLineSession;
import org.frankframework.core.PipeRunException;
import org.frankframework.core.PipeRunResult;
import org.frankframework.pipes.hash.Algorithm;
import org.frankframework.stream.Message;

/**
* Pipe to calculate checksum on input.
*
* This pipe can be used to generate a hash for the given message using an algorithm. With this, you can prove integrity of the message. If you
* need to prove the authenticity of the message as well, please use the {@link HashPipe} which uses an algorithm and a secret to prove both
* integrity and authenticity.
* <p>
* The hash is generated based on the bytes of the given input message or on the bytes read from the file path if @{code inputIsFile} is @{code true}
* <p>
* The supported algorithms are:
* <ul>
* <li>CRC32</li>
* <li>Adler32</li>
* <li>MD5</li>
* <li>SHA</li>
* <li>SHA256</li>
* <li>SHA384</li>
* <li>SHA512</li>
* </ul>
*
* @author Gerrit van Brakel
* @since 4.9
* @author Gerrit van Brakel
* @since 4.9
* @deprecated please use the {@link HashPipe}
*/
public class ChecksumPipe extends FixedForwardPipe {
@Deprecated(forRemoval = true, since = "8.3.0")
@ConfigurationWarning("Use the HashPipe")
public class ChecksumPipe extends HashPipe {

private @Getter String charset;
private @Getter ChecksumType type=ChecksumType.MD5;
private @Getter boolean inputIsFile;

public enum ChecksumType {
MD5,
SHA,
SHA256("SHA-256"),
SHA512("SHA-512"),
CRC32,
ADLER32;

private final String algorithm;

ChecksumType(String algorithm) {
this.algorithm = algorithm;
}
ChecksumType() {
this(null);
}

public String getAlgorithm() {
return algorithm!=null ? algorithm : name();
}
}

protected interface ChecksumGenerator {
void update(int b);
void update(byte[] b, int offset, int length);
String getResult();
}

protected ChecksumGenerator createChecksumGenerator() throws NoSuchAlgorithmException {
switch(getType()) {
case MD5:
case SHA:
case SHA256:
case SHA512:
return new MessageDigestChecksumGenerator(getType());
case CRC32:
return new ZipChecksumGenerator(new CRC32());
case ADLER32:
return new ZipChecksumGenerator(new Adler32());
default:
throw new NoSuchAlgorithmException("unsupported algorithm ["+getType()+"]");
}
}

protected static class ZipChecksumGenerator implements ChecksumGenerator {

private final Checksum checksum;

ZipChecksumGenerator(Checksum checksum) {
super();
this.checksum=checksum;
checksum.reset();
}

@Override
public void update(int b){
checksum.update(b);
}

@Override
public void update(byte[] b, int offset, int length){
checksum.update(b,offset,length);
}

@Override
public String getResult(){
return Long.toHexString(checksum.getValue());
}
}

protected static class MessageDigestChecksumGenerator implements ChecksumGenerator {

private final MessageDigest messageDigest;

MessageDigestChecksumGenerator(ChecksumType type) throws NoSuchAlgorithmException {
super();
this.messageDigest=MessageDigest.getInstance(type.getAlgorithm());
}

@Override
public void update(int b){
messageDigest.update((byte)b);
@Override
public void configure() throws ConfigurationException {
// Set the defaults for this Pipe, these are different compared to the HashPipe
if (getAlgorithm() == null) {
setAlgorithm(Algorithm.MD5);
}

@Override
public void update(byte[] b, int offset, int length){
messageDigest.update(b,offset,length);
if (getHashEncoding() == null) {
setHashEncoding(HashPipe.HashEncoding.Hex);
}

@Override
public String getResult(){
return new BigInteger(1,messageDigest.digest()).toString(16);
}
super.configure();
}

@Override
public PipeRunResult doPipe(Message message, PipeLineSession session) throws PipeRunException {
try {
ChecksumGenerator cg=createChecksumGenerator();
byte[] barr=new byte[1000];
try (InputStream fis = isInputIsFile() ? new FileInputStream(message.asString()) : message.asInputStream(getCharset())){
int c;
while ((c=fis.read(barr))>=0) {
cg.update(barr, 0, c);
}
}
return new PipeRunResult(getSuccessForward(), cg.getResult());
} catch (Exception e) {
throw new PipeRunException(this,"cannot calculate ["+getType()+"]"+(isInputIsFile()?" on file ["+message+"]":" using charset ["+getCharset()+"]"),e);
}
}

/**
* Character encoding to be used to encode message before calculating checksum.
*/
public void setCharset(String string) {
charset = string;
}
try (InputStream fis = isInputIsFile() ? new FileInputStream(message.asString()) : message.asInputStream(getCharset())) {

/**
* Type of checksum to be calculated
* @ff.default MD5
*/
public void setType(ChecksumType value) {
type = value;
return super.doPipe(new Message(fis, message.getContext()), session);
} catch (IOException e) {
throw new PipeRunException(this, "Error reading input" + (isInputIsFile() ? " file [" + message + "]" : " using charset [" + getCharset() + "]"), e);
}
}

/**
* If set <code>true</code>, the input is assumed to be a filename; otherwise the input itself is used in the calculations.
*
* @ff.default false
*/
@Deprecated(forRemoval = true, since = "7.7.0")
Expand All @@ -181,4 +92,13 @@ public void setInputIsFile(boolean b) {
inputIsFile = b;
}

/**
* Type of checksum to be calculated
* @ff.default MD5
*/
@Deprecated(forRemoval = true, since = "8.3.0")
@ConfigurationWarning("Please use setAlgorithm to set the algorithm")
public void setType(Algorithm value) {
setAlgorithm(value);
}
}
99 changes: 50 additions & 49 deletions core/src/main/java/org/frankframework/pipes/FixedResultPipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@

import javax.xml.transform.TransformerException;

import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXException;

import org.frankframework.configuration.ConfigurationException;
import org.frankframework.configuration.ConfigurationWarning;
import org.frankframework.core.ParameterException;
Expand All @@ -41,9 +44,6 @@
import org.frankframework.util.ClassLoaderUtils;
import org.frankframework.util.StringResolver;
import org.frankframework.util.TransformerPool;
import org.xml.sax.SAXException;

import lombok.Getter;

/**
* This Pipe opens and returns a file from the classpath. The filename is a mandatory parameter to use. You can
Expand Down Expand Up @@ -81,7 +81,7 @@
* <pipe name="make unique message" className="org.frankframework.pipes.FixedResultPipe"
* returnString="&lt;msg mid=&quot;MID&quot; action=&quot;ACTION&quot; /&gt;" replaceFixedParams="true">
* <param name="MID" sessionKey="mid" />
* <param name="ACTION" xpathExpression="request/@action" />
* <param name="ACTION" xpathExpression="request/@action" />
* </pipe>
* }
* </pre>
Expand All @@ -91,8 +91,8 @@
* {@code
* <pipe name="make unique message" className="org.frankframework.pipes.ReplacerPipe"
* getInputFromFixedValue="&lt;msg mid=&quot;?{MID}&quot; action=&quot;?{ACTION}&quot; /&gt;">
* <param name="MID" sessionKey="mid" />
* <param name="ACTION" xpathExpression="request/@action" />
* <param name="MID" sessionKey="mid" />
* <param name="ACTION" xpathExpression="request/@action" />
* </pipe>
* }
* </pre>
Expand Down Expand Up @@ -129,59 +129,59 @@
* was also used to store information in the session. For example, a port of configuration in the JMS listener sender configuration looked like this:
* <pre>
* {@code
* <CompareStringPipe name="compareIdAndCid" >
* <param name="operand1" sessionKey="id"/>
* <param name="operand2" sessionKey="cid"/>
* <forward name="equals" path="IdAndCidSame" />
* <forward name="lessthan" path="IdAndCidDifferent" />
* <forward name="greaterthan" path="IdAndCidDifferent" />
* </CompareStringPipe>
* <FixedResultPipe name="IdAndCidSame" returnString="true" storeResultInSessionKey="IdAndCidSame">
* <forward name="success" path="displayKeys" />
* </FixedResultPipe>
* <FixedResultPipe name="IdAndCidDifferent" returnString="false" storeResultInSessionKey="IdAndCidSame">
* <forward name="success" path="displayKeys" />
* </FixedResultPipe>
* <CompareStringPipe name="compareIdAndCid" >
* <param name="operand1" sessionKey="id"/>
* <param name="operand2" sessionKey="cid"/>
* <forward name="equals" path="IdAndCidSame" />
* <forward name="lessthan" path="IdAndCidDifferent" />
* <forward name="greaterthan" path="IdAndCidDifferent" />
* </CompareStringPipe>
* <FixedResultPipe name="IdAndCidSame" returnString="true" storeResultInSessionKey="IdAndCidSame">
* <forward name="success" path="displayKeys" />
* </FixedResultPipe>
* <FixedResultPipe name="IdAndCidDifferent" returnString="false" storeResultInSessionKey="IdAndCidSame">
* <forward name="success" path="displayKeys" />
* </FixedResultPipe>
*
* <pipe name="displayKeys" className="org.frankframework.pipes.FixedResultPipe"
* returnString="branch [BRANCH] Orignal Id [MID] cid [CID] id=cid [SAME]" replaceFixedParams="true">
* <param name="BRANCH" sessionKey="originalMessage" xpathExpression="*&#47;@branch" />
* <param name="MID" sessionKey="id" />
* <param name="CID" sessionKey="cid" />
* <param name="SAME" sessionKey="IdAndCidSame" />
* <forward name="success" path="EXIT" />
* </pipe>
* <pipe name="displayKeys" className="org.frankframework.pipes.FixedResultPipe"
* returnString="branch [BRANCH] Orignal Id [MID] cid [CID] id=cid [SAME]" replaceFixedParams="true">
* <param name="BRANCH" sessionKey="originalMessage" xpathExpression="*&#47;@branch" />
* <param name="MID" sessionKey="id" />
* <param name="CID" sessionKey="cid" />
* <param name="SAME" sessionKey="IdAndCidSame" />
* <forward name="success" path="EXIT" />
* </pipe>
* }
* </pre>
*
* Was rewritten to the following:
* <pre>
* {@code
* <CompareStringPipe name="compareIdAndCid" >
* <param name="operand1" sessionKey="id"/>
* <param name="operand2" sessionKey="cid"/>
* <forward name="equals" path="IdAndCidSame" />
* <forward name="lessthan" path="IdAndCidDifferent" />
* <forward name="greaterthan" path="IdAndCidDifferent" />
* </CompareStringPipe>
* <CompareStringPipe name="compareIdAndCid" >
* <param name="operand1" sessionKey="id"/>
* <param name="operand2" sessionKey="cid"/>
* <forward name="equals" path="IdAndCidSame" />
* <forward name="lessthan" path="IdAndCidDifferent" />
* <forward name="greaterthan" path="IdAndCidDifferent" />
* </CompareStringPipe>
*
* <PutInSessionPipe name="IdAndCidSame" value="true" sessionKey="IdAndCidSame">
* <forward name="success" path="putOriginalMessageInSession" />
* </PutInSessionPipe>
* <PutInSessionPipe name="IdAndCidDifferent" value="false" sessionKey="IdAndCidSame">
* <forward name="success" path="putOriginalMessageInSession" />
* </PutInSessionPipe>
* <PutInSessionPipe name="IdAndCidSame" value="true" sessionKey="IdAndCidSame">
* <forward name="success" path="putOriginalMessageInSession" />
* </PutInSessionPipe>
* <PutInSessionPipe name="IdAndCidDifferent" value="false" sessionKey="IdAndCidSame">
* <forward name="success" path="putOriginalMessageInSession" />
* </PutInSessionPipe>
*
* <PutInSessionPipe name="putOriginalMessageInSession" sessionKey="incomingMessage"/>
* <PutInSessionPipe name="putOriginalMessageInSession" sessionKey="incomingMessage"/>
*
* <pipe name="displayKeys" className="org.frankframework.pipes.ReplacerPipe"
* getInputFromFixedValue="branch [?{BRANCH}] Original Id [?{MID}] cid [?{CID}] id=cid [?{SAME}]">
* <param name="BRANCH" sessionKey="originalMessage" xpathExpression="*&#47;@branch" />
* <param name="MID" sessionKey="id" />
* <param name="CID" sessionKey="cid" />
* <param name="SAME" sessionKey="IdAndCidSame" />
* <forward name="success" path="EXIT" />
* </pipe>
* <pipe name="displayKeys" className="org.frankframework.pipes.ReplacerPipe"
* getInputFromFixedValue="branch [?{BRANCH}] Original Id [?{MID}] cid [?{CID}] id=cid [?{SAME}]">
* <param name="BRANCH" sessionKey="originalMessage" xpathExpression="*&#47;@branch" />
* <param name="MID" sessionKey="id" />
* <param name="CID" sessionKey="cid" />
* <param name="SAME" sessionKey="IdAndCidSame" />
* <forward name="success" path="EXIT" />
* </pipe>
* }
* </pre>
* <p>
Expand Down Expand Up @@ -246,6 +246,7 @@ public class FixedResultPipe extends FixedForwardPipe {
* If a filename or filenameSessionKey was specified, the contents of the file is put in the
* <code>returnString</code>, so that the <code>returnString</code>
* may always be returned.
*
* @throws ConfigurationException
*/
@Override
Expand Down
Loading

0 comments on commit 40970b7

Please sign in to comment.