зеркало из https://github.com/syncfusion/nifi.git
NIFI-1365
Added Groovy support for unit tests to pom with skeleton test. Added Groovy unit tests for OCSPCertificateValidator. Implemented positive & negative unit tests with cache injection for valid/revoked OCSP certificate. Modified pom.xml to support Groovy unit tests with custom variable. mvn clean test -Dgroovy=test Added local cache injection into Groovy tests for OCSP certificate validation (see NIFI-1324 and NIFI-1364). Set Java version to 1.7 for Groovy test src/target. Moved Groovy unit test profile from nifi-web-security to root pom. Added null check for algorithm argument in PGPUtil. Changed buffer length check from ">= 0" to "> -1" because it was confusing other developers. Resolved contrib-check line length issues. Fixed contrib-check issues in OpenPGPKeyBasedEncryptorTest. This closes #163 Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>
This commit is contained in:
Родитель
0d45f21f4b
Коммит
93aac8cff3
|
@ -16,4 +16,4 @@ before_install:
|
|||
- sed -e "s/^\\(127\\.0\\.0\\.1.*\\)/\\1 $(hostname | cut -c1-63)/" /etc/hosts | sudo tee /etc/hosts
|
||||
- sed -i.bak -e 's|https://nexus.codehaus.org/snapshots/|https://oss.sonatype.org/content/repositories/codehaus-snapshots/|g' ~/.m2/settings.xml
|
||||
|
||||
script: mvn clean install -Pcontrib-check
|
||||
script: mvn clean install -Pcontrib-check,groovy-unit-test
|
||||
|
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* 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.security.x509.ocsp
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
||||
import com.google.common.cache.LoadingCache
|
||||
import com.sun.jersey.api.client.Client
|
||||
import org.apache.nifi.util.NiFiProperties
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import org.bouncycastle.operator.OperatorCreationException
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import org.junit.*
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.security.*
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import static groovy.test.GroovyAssert.shouldFail
|
||||
import static org.junit.Assert.fail
|
||||
|
||||
public class OcspCertificateValidatorGroovyTest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OcspCertificateValidatorGroovyTest.class);
|
||||
|
||||
private static final int KEY_SIZE = 2048;
|
||||
|
||||
private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000;
|
||||
private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000;
|
||||
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
|
||||
private static final String PROVIDER = "BC";
|
||||
|
||||
private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US";
|
||||
private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US";
|
||||
|
||||
private NiFiProperties mockProperties
|
||||
|
||||
// System under test
|
||||
OcspCertificateValidator certificateValidator
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockProperties = [getProperty: { String propertyName -> return "value_for_${propertyName}" }] as NiFiProperties
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
certificateValidator?.metaClass = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a public/private RSA keypair using the default key size.
|
||||
*
|
||||
* @return the keypair
|
||||
* @throws NoSuchAlgorithmException if the RSA algorithm is not available
|
||||
*/
|
||||
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(KEY_SIZE);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate using an on-demand keypair.
|
||||
*
|
||||
* @param dn the DN
|
||||
* @return the certificate
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws SignatureException
|
||||
* @throws InvalidKeyException
|
||||
* @throws OperatorCreationException
|
||||
*/
|
||||
private
|
||||
static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
return generateCertificate(dn, keyPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signed certificate with a specific keypair.
|
||||
*
|
||||
* @param dn the DN
|
||||
* @param keyPair the public key will be included in the certificate and the the private key is used to sign the certificate
|
||||
* @return the certificate
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws SignatureException
|
||||
* @throws InvalidKeyException
|
||||
* @throws OperatorCreationException
|
||||
*/
|
||||
private
|
||||
static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey);
|
||||
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
|
||||
Date startDate = new Date(YESTERDAY);
|
||||
Date endDate = new Date(ONE_YEAR_FROM_NOW);
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
|
||||
new X500Name(dn),
|
||||
BigInteger.valueOf(System.currentTimeMillis()),
|
||||
startDate, endDate,
|
||||
new X500Name(dn),
|
||||
subPubKeyInfo);
|
||||
|
||||
// Set certificate extensions
|
||||
// (1) digitalSignature extension
|
||||
certBuilder.addExtension(X509Extension.keyUsage, true,
|
||||
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement));
|
||||
|
||||
// (2) extendedKeyUsage extension
|
||||
Vector<KeyPurposeId> ekUsages = new Vector<>();
|
||||
ekUsages.add(KeyPurposeId.id_kp_clientAuth);
|
||||
ekUsages.add(KeyPurposeId.id_kp_serverAuth);
|
||||
certBuilder.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages));
|
||||
|
||||
// Sign the certificate
|
||||
X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
|
||||
return new JcaX509CertificateConverter().setProvider(PROVIDER)
|
||||
.getCertificate(certificateHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a certificate signed by the issuer key.
|
||||
*
|
||||
* @param dn the subject DN
|
||||
* @param issuerDn the issuer DN
|
||||
* @param issuerKey the issuer private key
|
||||
* @return the certificate
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws SignatureException
|
||||
* @throws InvalidKeyException
|
||||
* @throws OperatorCreationException
|
||||
*/
|
||||
private
|
||||
static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a certificate with a specific public key signed by the issuer key.
|
||||
*
|
||||
* @param dn the subject DN
|
||||
* @param publicKey the subject public key
|
||||
* @param issuerDn the issuer DN
|
||||
* @param issuerKey the issuer private key
|
||||
* @return the certificate
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws SignatureException
|
||||
* @throws InvalidKeyException
|
||||
* @throws OperatorCreationException
|
||||
*/
|
||||
private
|
||||
static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
|
||||
ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey);
|
||||
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
|
||||
Date startDate = new Date(YESTERDAY);
|
||||
Date endDate = new Date(ONE_YEAR_FROM_NOW);
|
||||
|
||||
X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
|
||||
new X500Name(issuerDn),
|
||||
BigInteger.valueOf(System.currentTimeMillis()),
|
||||
startDate, endDate,
|
||||
new X500Name(dn),
|
||||
subPubKeyInfo);
|
||||
|
||||
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
|
||||
return new JcaX509CertificateConverter().setProvider(PROVIDER)
|
||||
.getCertificate(certificateHolder);
|
||||
}
|
||||
|
||||
private static X509Certificate[] generateCertificateChain(String dn = SUBJECT_DN, String issuerDn = ISSUER_DN) {
|
||||
final KeyPair issuerKeyPair = generateKeyPair();
|
||||
final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
|
||||
|
||||
final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
|
||||
final X509Certificate certificate = generateIssuedCertificate(dn, issuerDn, issuerPrivateKey);
|
||||
[certificate, issuerCertificate] as X509Certificate[]
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldGenerateCertificate() throws Exception {
|
||||
// Arrange
|
||||
final String testDn = "CN=This is a test";
|
||||
|
||||
// Act
|
||||
X509Certificate certificate = generateCertificate(testDn);
|
||||
logger.info("Generated certificate: \n{}", certificate);
|
||||
|
||||
// Assert
|
||||
assert certificate.getSubjectDN().getName().equals(testDn);
|
||||
assert certificate.getIssuerDN().getName().equals(testDn);
|
||||
certificate.verify(certificate.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldGenerateCertificateFromKeyPair() throws Exception {
|
||||
// Arrange
|
||||
final String testDn = "CN=This is a test";
|
||||
final KeyPair keyPair = generateKeyPair();
|
||||
|
||||
// Act
|
||||
X509Certificate certificate = generateCertificate(testDn, keyPair);
|
||||
logger.info("Generated certificate: \n{}", certificate);
|
||||
|
||||
// Assert
|
||||
assert certificate.getPublicKey().equals(keyPair.getPublic());
|
||||
assert certificate.getSubjectDN().getName().equals(testDn);
|
||||
assert certificate.getIssuerDN().getName().equals(testDn);
|
||||
certificate.verify(certificate.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldGenerateIssuedCertificate() throws Exception {
|
||||
// Arrange
|
||||
final String testDn = "CN=This is a signed test";
|
||||
final String issuerDn = "CN=Issuer CA";
|
||||
final KeyPair issuerKeyPair = generateKeyPair();
|
||||
final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
|
||||
|
||||
final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
|
||||
logger.info("Generated issuer certificate: \n{}", issuerCertificate);
|
||||
|
||||
// Act
|
||||
X509Certificate certificate = generateIssuedCertificate(testDn, issuerDn, issuerPrivateKey);
|
||||
logger.info("Generated signed certificate: \n{}", certificate);
|
||||
|
||||
// Assert
|
||||
assert issuerCertificate.getPublicKey().equals(issuerKeyPair.getPublic());
|
||||
assert certificate.getSubjectX500Principal().getName().equals(testDn);
|
||||
assert certificate.getIssuerX500Principal().getName().equals(issuerDn);
|
||||
certificate.verify(issuerCertificate.getPublicKey());
|
||||
|
||||
try {
|
||||
certificate.verify(certificate.getPublicKey());
|
||||
fail("Should have thrown exception");
|
||||
} catch (Exception e) {
|
||||
assert e instanceof SignatureException;
|
||||
assert e.getMessage().contains("certificate does not verify with supplied key");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldValidateCertificate() throws Exception {
|
||||
// Arrange
|
||||
X509Certificate[] certificateChain = generateCertificateChain();
|
||||
|
||||
certificateValidator = new OcspCertificateValidator(mockProperties)
|
||||
|
||||
// Must populate the client even though it is not used in this check
|
||||
certificateValidator.client = new Client()
|
||||
|
||||
// Form a map of the request to a good status and load it into the cache
|
||||
OcspRequest revokedRequest = new OcspRequest(certificateChain.first(), certificateChain.last())
|
||||
OcspStatus revokedStatus = new OcspStatus()
|
||||
revokedStatus.responseStatus = OcspStatus.ResponseStatus.Successful
|
||||
revokedStatus.validationStatus = OcspStatus.ValidationStatus.Good
|
||||
revokedStatus.verificationStatus = OcspStatus.VerificationStatus.Verified
|
||||
LoadingCache<OcspRequest, OcspStatus> cacheWithRevokedCertificate = buildCacheWithContents([(revokedRequest): revokedStatus])
|
||||
certificateValidator.ocspCache = cacheWithRevokedCertificate
|
||||
|
||||
// Act
|
||||
certificateValidator.validate(certificateChain)
|
||||
|
||||
// Assert
|
||||
assert true
|
||||
}
|
||||
|
||||
// TODO - NIFI-1364
|
||||
@Ignore("To be implemented with Groovy test")
|
||||
@Test
|
||||
public void testShouldNotValidateEmptyCertificate() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldNotValidateRevokedCertificate() throws Exception {
|
||||
// Arrange
|
||||
X509Certificate[] certificateChain = generateCertificateChain();
|
||||
|
||||
certificateValidator = new OcspCertificateValidator(mockProperties)
|
||||
|
||||
// Must populate the client even though it is not used in this check
|
||||
certificateValidator.client = new Client()
|
||||
|
||||
// Form a map of the request to a revoked status and load it into the cache
|
||||
OcspRequest revokedRequest = new OcspRequest(certificateChain.first(), certificateChain.last())
|
||||
OcspStatus revokedStatus = new OcspStatus()
|
||||
revokedStatus.responseStatus = OcspStatus.ResponseStatus.Successful
|
||||
revokedStatus.validationStatus = OcspStatus.ValidationStatus.Revoked
|
||||
revokedStatus.verificationStatus = OcspStatus.VerificationStatus.Verified
|
||||
LoadingCache<OcspRequest, OcspStatus> cacheWithRevokedCertificate = buildCacheWithContents([(revokedRequest): revokedStatus])
|
||||
certificateValidator.ocspCache = cacheWithRevokedCertificate
|
||||
|
||||
// Act
|
||||
def msg = shouldFail(CertificateStatusException) {
|
||||
certificateValidator.validate(certificateChain)
|
||||
}
|
||||
|
||||
// Assert
|
||||
assert msg =~ "is revoked according to the certificate authority"
|
||||
}
|
||||
|
||||
LoadingCache<OcspRequest, OcspStatus> buildCacheWithContents(Map map) {
|
||||
CacheBuilder.newBuilder().build(new CacheLoader<OcspRequest, OcspStatus>() {
|
||||
@Override
|
||||
public OcspStatus load(OcspRequest ocspRequest) throws Exception {
|
||||
logger.info("Mock cache implementation load(${ocspRequest}) returns ${map.get(ocspRequest)}")
|
||||
return map.get(ocspRequest) as OcspStatus
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO - NIFI-1364
|
||||
@Ignore("To be implemented with Groovy test")
|
||||
@Test
|
||||
public void testValidateShouldHandleUnsignedResponse() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
// TODO - NIFI-1364
|
||||
@Ignore("To be implemented with Groovy test")
|
||||
@Test
|
||||
public void testValidateShouldHandleResponseWithIncorrectNonce() throws Exception {
|
||||
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.apache.nifi.processors.standard.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.processors.standard.EncryptContent;
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
|
@ -46,9 +47,11 @@ public class PGPUtil {
|
|||
public static final int BUFFER_SIZE = 65536;
|
||||
public static final int BLOCK_SIZE = 4096;
|
||||
|
||||
public static void encrypt(InputStream in, OutputStream out, String algorithm, String provider, int cipher, String filename,
|
||||
PGPKeyEncryptionMethodGenerator encryptionMethodGenerator) throws IOException, PGPException {
|
||||
|
||||
public static void encrypt(InputStream in, OutputStream out, String algorithm, String provider, int cipher, String filename, PGPKeyEncryptionMethodGenerator encryptionMethodGenerator) throws
|
||||
IOException, PGPException {
|
||||
if (StringUtils.isEmpty(algorithm)) {
|
||||
throw new IllegalArgumentException("The algorithm must be specified");
|
||||
}
|
||||
final boolean isArmored = EncryptContent.isPGPArmoredAlgorithm(algorithm);
|
||||
OutputStream output = out;
|
||||
if (isArmored) {
|
||||
|
@ -76,7 +79,7 @@ public class PGPUtil {
|
|||
|
||||
final byte[] buffer = new byte[BLOCK_SIZE];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) >= 0) {
|
||||
while ((len = in.read(buffer)) > -1) {
|
||||
literalOut.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
|
88
pom.xml
88
pom.xml
|
@ -125,7 +125,7 @@ language governing permissions and limitations under the License. -->
|
|||
</repository>
|
||||
<repository>
|
||||
<id>jcenter</id>
|
||||
<url>http://jcenter.bintray.com </url>
|
||||
<url>http://jcenter.bintray.com</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
|
@ -1491,5 +1491,91 @@ language governing permissions and limitations under the License. -->
|
|||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- Custom profile for Groovy tests -->
|
||||
<id>groovy-unit-test</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>groovy</name>
|
||||
<value>test</value>
|
||||
</property>
|
||||
</activation>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>1.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/main/groovy</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>add-test-source</id>
|
||||
<phase>generate-test-sources</phase>
|
||||
<goals>
|
||||
<goal>add-test-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/test/groovy</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.2</version>
|
||||
<configuration>
|
||||
<compilerId>groovy-eclipse-compiler</compilerId>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<includes>
|
||||
<include>**/*.java</include>
|
||||
<include>**/*.groovy</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-eclipse-compiler</artifactId>
|
||||
<version>2.9.2-01</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-eclipse-batch</artifactId>
|
||||
<version>2.4.3-01</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-eclipse-compiler</artifactId>
|
||||
<version>2.9.2-01</version>
|
||||
<extensions>true</extensions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
Загрузка…
Ссылка в новой задаче