/*
*
* Copyright (c) 2013 - 2017 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.pki.ca.server.impl;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.EndOfQueue;
import org.xipki.commons.common.ProcessLog;
import org.xipki.commons.common.QueueEntry;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.X509Cert;
import org.xipki.pki.ca.api.NameId;
import org.xipki.pki.ca.api.OperationException;
import org.xipki.pki.ca.api.publisher.x509.X509CertificateInfo;
import org.xipki.pki.ca.server.impl.store.CertificateStore;
/**
* @author Lijun Liao
* @since 2.1.0
*/
class CertRepublisher {
private class SerialWithIdQueueEntry implements QueueEntry {
private final SerialWithId serialWithId;
public SerialWithIdQueueEntry(final SerialWithId serialWithId) {
this.serialWithId = ParamUtil.requireNonNull("serialWithId", serialWithId);
}
public SerialWithId getSerialWithId() {
return serialWithId;
}
}
private class CertRepublishProducer implements Runnable {
private boolean failed;
private CertRepublishProducer() {
}
@Override
public void run() {
final int numEntries = 100;
long startId = 1;
try {
List<SerialWithId> serials;
do {
serials = certstore.getCertSerials(ca, startId, numEntries,
onlyRevokedCerts);
long maxId = 1;
for (SerialWithId sid : serials) {
if (sid.getId() > maxId) {
maxId = sid.getId();
}
queue.put(new SerialWithIdQueueEntry(sid));
}
startId = maxId + 1;
}
while (serials.size() >= numEntries && !failed && !stopMe.get());
queue.put(EndOfQueue.INSTANCE);
} catch (OperationException ex) {
LogUtil.error(LOG, ex, "error in RepublishProducer");
failed = true;
} catch (InterruptedException ex) {
LogUtil.error(LOG, ex, "error in RepublishProducer");
failed = true;
}
if (!queue.contains(EndOfQueue.INSTANCE)) {
try {
queue.put(EndOfQueue.INSTANCE);
} catch (InterruptedException ex) {
LogUtil.error(LOG, ex, "error in RepublishProducer");
failed = true;
}
}
}
}
private class CertRepublishConsumer implements Runnable {
private boolean failed;
private CertRepublishConsumer() {
}
@Override
public void run() {
while (!failed) {
QueueEntry entry;
try {
entry = queue.take();
} catch (InterruptedException ex) {
LogUtil.error(LOG, ex, "could not take from queue");
failed = true;
break;
}
if (entry instanceof EndOfQueue) {
// re-add it to queue so that other consumers know it
try {
queue.put(entry);
} catch (InterruptedException ex) {
LogUtil.warn(LOG, ex, "could not re-add EndOfQueue to queue");
}
break;
}
SerialWithId sid = ((SerialWithIdQueueEntry) entry).getSerialWithId();
X509CertificateInfo certInfo;
try {
certInfo = certstore.getCertificateInfoForId(ca, caCert, sid.getId(),
caIdNameMap);
} catch (OperationException | CertificateException ex) {
LogUtil.error(LOG, ex);
failed = true;
break;
}
boolean allSucc = true;
for (IdentifiedX509CertPublisher publisher : publishers) {
if (!certInfo.isRevoked() && !publisher.publishsGoodCert()) {
continue;
}
boolean successful = publisher.certificateAdded(certInfo);
if (!successful) {
LOG.error("republish certificate serial={} to publisher {} failed",
LogUtil.formatCsn(sid.getSerial()),
publisher.getIdent());
allSucc = false;
}
}
if (!allSucc) {
break;
}
processLog.addNumProcessed(1);
}
}
}
private static final Logger LOG = LoggerFactory.getLogger(CertRepublisher.class);
private final NameId ca;
private final X509Cert caCert;
private final CaIdNameMap caIdNameMap;
private final CertificateStore certstore;
private final List<IdentifiedX509CertPublisher> publishers;
private final boolean onlyRevokedCerts;
private final int numThreads;
private final BlockingQueue<QueueEntry> queue = new ArrayBlockingQueue<>(1000);
private final AtomicBoolean stopMe = new AtomicBoolean(false);
private ProcessLog processLog;
CertRepublisher(final NameId ca, final X509Cert caCert, final CaIdNameMap caIdNameMap,
final CertificateStore certstore, final List<IdentifiedX509CertPublisher> publishers,
final boolean onlyRevokedCerts, final int numThreads) {
this.ca = ParamUtil.requireNonNull("ca", ca);
this.caCert = ParamUtil.requireNonNull("caCert", caCert);
this.caIdNameMap = ParamUtil.requireNonNull("caIdNameMap", caIdNameMap);
this.certstore = ParamUtil.requireNonNull("certstore", certstore);
this.publishers = ParamUtil.requireNonEmpty("publishers", publishers);
this.onlyRevokedCerts = onlyRevokedCerts;
this.numThreads = ParamUtil.requireMin("numThreads", numThreads, 1);
}
boolean republish() {
try {
return doRepublish();
} finally {
if (processLog != null) {
processLog.finish();
processLog.printTrailer();
}
}
}
private boolean doRepublish() {
long total;
try {
total = certstore.getCountOfCerts(ca, onlyRevokedCerts);
} catch (OperationException ex) {
LogUtil.error(LOG, ex, "could not getCountOfCerts");
return false;
}
processLog = new ProcessLog(total);
processLog.printHeader();
ExecutorService executor = Executors.newFixedThreadPool(numThreads + 1);
List<CertRepublishConsumer> consumers = new ArrayList<>(numThreads);
AtomicBoolean stopMe = new AtomicBoolean(false);
for (int i = 0; i < numThreads; i++) {
CertRepublishConsumer consumer = new CertRepublishConsumer();
consumers.add(consumer);
}
CertRepublishProducer producer = new CertRepublishProducer();
executor.execute(producer);
for (CertRepublishConsumer consumer : consumers) {
executor.execute(consumer);
}
executor.shutdown();
boolean successful = true;
while (true) {
processLog.printStatus();
if (successful) {
if (producer.failed) {
successful = false;
}
if (successful) {
for (CertRepublishConsumer consumer : consumers) {
if (consumer.failed) {
successful = false;
break;
}
}
}
if (!successful) {
stopMe.set(true);
LOG.warn("failed");
}
}
try {
boolean terminated = executor.awaitTermination(1, TimeUnit.SECONDS);
if (terminated) {
break;
}
} catch (InterruptedException ex) {
stopMe.set(true);
LogUtil.warn(LOG, ex, "interrupted: " + ex.getMessage());
}
}
if (successful) {
if (producer.failed) {
successful = false;
}
if (successful) {
for (CertRepublishConsumer consumer : consumers) {
if (consumer.failed) {
successful = false;
break;
}
}
}
if (!successful) {
LOG.warn("failed");
}
}
return successful;
}
}