Java tutorial
/* * * This file is part of the XiPKI project. * Copyright (c) 2014 - 2015 Lijun Liao * Author: Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.ca.client.shell.loadtest; import java.math.BigInteger; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicInteger; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.Certificate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.ca.client.api.CAClient; import org.xipki.ca.client.api.CAClientException; import org.xipki.ca.client.api.CertIdOrError; import org.xipki.ca.client.api.PKIErrorException; import org.xipki.ca.client.api.dto.RevokeCertRequestEntryType; import org.xipki.ca.client.api.dto.RevokeCertRequestType; import org.xipki.common.AbstractLoadTest; import org.xipki.common.CRLReason; import org.xipki.common.ParamChecker; import org.xipki.common.util.CollectionUtil; import org.xipki.common.util.SecurityUtil; import org.xipki.datasource.api.DataSourceWrapper; import org.xipki.datasource.api.exception.DataAccessException; /** * @author Lijun Liao */ class CALoadTestRevoke extends AbstractLoadTest { private static final Logger LOG = LoggerFactory.getLogger(CALoadTestRevoke.class); private final CAClient caClient; private final DataSourceWrapper caDataSource; private final X500Name caSubject; private final Set<Long> excludeSerials = new HashSet<>(); private final ConcurrentLinkedDeque<Long> serials = new ConcurrentLinkedDeque<>(); private final int caInfoId; private final long minSerial; private final long maxSerial; private final int maxCerts; private final int n; private AtomicInteger processedCerts = new AtomicInteger(0); private long nextStartSerial; private boolean noUnrevokedCerts = false; private CRLReason[] reasons = { CRLReason.UNSPECIFIED, CRLReason.KEY_COMPROMISE, CRLReason.AFFILIATION_CHANGED, CRLReason.SUPERSEDED, CRLReason.CESSATION_OF_OPERATION, CRLReason.CERTIFICATE_HOLD, CRLReason.PRIVILEGE_WITHDRAWN }; @Override protected Runnable getTestor() throws Exception { return new Testor(); } public CALoadTestRevoke(final CAClient caClient, final Certificate caCert, final DataSourceWrapper caDataSource, final int maxCerts, final int n) throws Exception { ParamChecker.assertNotNull("caClient", caClient); ParamChecker.assertNotNull("caCert", caCert); ParamChecker.assertNotNull("caDataSource", caDataSource); if (n < 1) { throw new IllegalArgumentException("non-positive n " + n + " is not allowed"); } this.n = n; this.caClient = caClient; this.caDataSource = caDataSource; this.caSubject = caCert.getSubject(); this.maxCerts = maxCerts; if (caCert.getIssuer().equals(caCert.getSubject())) { this.excludeSerials.add(caCert.getSerialNumber().getPositiveValue().longValue()); } String sha1Fp = SecurityUtil.sha1sum(caCert.getEncoded()); String sql = "SELECT ID FROM CS_CA WHERE FP_CERT='" + sha1Fp + "'"; Statement stmt = caDataSource.getConnection().createStatement(); try { ResultSet rs = stmt.executeQuery(sql); if (rs.next()) { caInfoId = rs.getInt("ID"); } else { throw new Exception("CA Certificate and database configuration does not match"); } rs.close(); sql = "SELECT MIN(SERIAL) FROM CERT WHERE REVOKED=0 AND CA_ID=" + caInfoId; rs = stmt.executeQuery(sql); rs.next(); minSerial = rs.getLong(1); nextStartSerial = minSerial; sql = "SELECT MAX(SERIAL) FROM CERT WHERE REVOKED=0 AND CA_ID=" + caInfoId; rs = stmt.executeQuery(sql); rs.next(); maxSerial = rs.getLong(1); } finally { caDataSource.releaseResources(stmt, null); } } private List<Long> nextSerials() throws DataAccessException { List<Long> ret = new ArrayList<>(n); for (int i = 0; i < n; i++) { Long serial = nextSerial(); if (serial != null) { ret.add(serial); } else { break; } } return ret; } private Long nextSerial() throws DataAccessException { synchronized (caDataSource) { if (maxCerts > 0) { int num = processedCerts.getAndAdd(1); if (num >= maxCerts) { return null; } } Long firstSerial = serials.pollFirst(); if (firstSerial != null) { return firstSerial; } if (noUnrevokedCerts == false) { String sql = "SERIAL FROM CERT WHERE REVOKED=0 AND CA_ID=" + caInfoId + " AND SERIAL > " + (nextStartSerial - 1) + " AND SERIAL < " + (maxSerial + 1); sql = caDataSource.createFetchFirstSelectSQL(sql, 1000, "SERIAL"); PreparedStatement stmt = null; ResultSet rs = null; int n = 0; try { stmt = caDataSource.getConnection().prepareStatement(sql); rs = stmt.executeQuery(); while (rs.next()) { n++; long serial = rs.getLong("SERIAL"); if (serial + 1 > nextStartSerial) { nextStartSerial = serial + 1; } if (excludeSerials.contains(serial) == false) { serials.addLast(serial); } } } catch (SQLException e) { throw caDataSource.translate(sql, e); } finally { caDataSource.releaseResources(stmt, rs); } if (n == 0) { System.out.println("no unrevoked certificate"); System.out.flush(); } if (n < 1000) { noUnrevokedCerts = true; } } return serials.pollFirst(); } } class Testor implements Runnable { @Override public void run() { while (stop() == false && getErrorAccout() < 1) { List<Long> serialNumbers; try { serialNumbers = nextSerials(); } catch (DataAccessException e) { account(1, 1); break; } if (CollectionUtil.isEmpty(serialNumbers)) { break; } boolean successful = testNext(serialNumbers); account(1, successful ? 0 : 1); } } private boolean testNext(final List<Long> serialNumbers) { RevokeCertRequestType request = new RevokeCertRequestType(); int id = 1; for (Long serialNumber : serialNumbers) { CRLReason reason = reasons[(int) (serialNumber % reasons.length)]; RevokeCertRequestEntryType entry = new RevokeCertRequestEntryType(Integer.toString(id++), caSubject, BigInteger.valueOf(serialNumber), reason.getCode(), null); request.addRequestEntry(entry); } Map<String, CertIdOrError> result; try { result = caClient.revokeCerts(request, null); } catch (CAClientException | PKIErrorException e) { LOG.warn("{}: {}", e.getClass().getName(), e.getMessage()); return false; } catch (Throwable t) { LOG.warn("{}: {}", t.getClass().getName(), t.getMessage()); return false; } if (result == null) { return false; } int nSuccess = 0; for (CertIdOrError entry : result.values()) { if (entry.getCertId() != null) { nSuccess++; } } return nSuccess == serialNumbers.size(); } } }