/*
* 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.brooklyn.core.mgmt.persist;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.CountdownTimer;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
public class ListeningObjectStore implements PersistenceObjectStore {
protected final PersistenceObjectStore delegate;
protected final List<ObjectStoreTransactionListener> listeners = MutableList.of();
private boolean writesFailSilently = false;
public static interface ObjectStoreTransactionListener {
public void recordQueryOut(String summary, int size);
public void recordDataOut(String summary, int size);
public void recordDataIn(String summary, int size);
}
public static class RecordingTransactionListener implements ObjectStoreTransactionListener {
private static final Logger log = LoggerFactory.getLogger(ListeningObjectStore.RecordingTransactionListener.class);
protected final String prefix;
protected final AtomicLong bytesIn = new AtomicLong();
protected final AtomicLong bytesOut = new AtomicLong();
protected final AtomicInteger countQueriesOut = new AtomicInteger();
protected final AtomicInteger countDataOut = new AtomicInteger();
protected final AtomicInteger countDataIn = new AtomicInteger();
public RecordingTransactionListener(String prefix) {
this.prefix = prefix;
}
public long getBytesIn() {
return bytesIn.get();
}
public long getBytesOut() {
return bytesOut.get();
}
public int getCountQueriesOut() {
return countQueriesOut.get();
}
public int getCountDataOut() {
return countDataOut.get();
}
public int getCountDataIn() {
return countDataIn.get();
}
public String getTotalString() {
return "totals: out="+Strings.makeSizeString(bytesOut.get())+" in="+Strings.makeSizeString(bytesIn.get());
}
@Override
public void recordQueryOut(String summary, int size) {
synchronized (this) { this.notifyAll(); }
bytesOut.addAndGet(size);
countQueriesOut.incrementAndGet();
log.info(prefix+" "+summary+" -->"+size+"; "+getTotalString());
}
@Override
public void recordDataOut(String summary, int size) {
synchronized (this) { this.notifyAll(); }
bytesOut.addAndGet(size);
countDataOut.incrementAndGet();
log.info(prefix+" "+summary+" -->"+size+"; "+getTotalString());
}
@Override
public void recordDataIn(String summary, int size) {
synchronized (this) { this.notifyAll(); }
bytesIn.addAndGet(size);
countDataIn.incrementAndGet();
log.info(prefix+" "+summary+" <--"+size+"; "+getTotalString());
}
public void blockUntilDataWrittenExceeds(long count, Duration timeout) throws InterruptedException, TimeoutException {
CountdownTimer timer = CountdownTimer.newInstanceStarted(timeout);
synchronized (this) {
while (bytesOut.get()<count) {
if (timer.isExpired())
throw new TimeoutException();
timer.waitOnForExpiry(this);
}
}
}
}
public ListeningObjectStore(PersistenceObjectStore delegate, ObjectStoreTransactionListener ...listeners) {
this.delegate = Preconditions.checkNotNull(delegate);
for (ObjectStoreTransactionListener listener: listeners)
this.listeners.add(listener);
}
@Override
public String getSummaryName() {
return delegate.getSummaryName();
}
@Override
public void prepareForMasterUse() {
delegate.prepareForMasterUse();
}
@Override
public StoreObjectAccessor newAccessor(String path) {
return new ListeningAccessor(path, delegate.newAccessor(path));
}
@Override
public void createSubPath(String subPath) {
if (writesFailSilently)
return;
for (ObjectStoreTransactionListener listener: listeners)
listener.recordQueryOut("creating path "+subPath, 1+subPath.length());
delegate.createSubPath(subPath);
}
@Override
public List<String> listContentsWithSubPath(String subPath) {
for (ObjectStoreTransactionListener listener: listeners)
listener.recordQueryOut("requesting list "+subPath, 1+subPath.length());
List<String> result = delegate.listContentsWithSubPath(subPath);
for (ObjectStoreTransactionListener listener: listeners)
listener.recordDataIn("receiving list "+subPath, result.toString().length());
return result;
}
@Override
public void close() {
delegate.close();
}
@Override
public void injectManagementContext(ManagementContext managementContext) {
delegate.injectManagementContext(managementContext);
}
@Override
public void prepareForSharedUse(PersistMode persistMode, HighAvailabilityMode haMode) {
delegate.prepareForSharedUse(persistMode, haMode);
}
@Override
public void deleteCompletely() {
for (ObjectStoreTransactionListener listener: listeners)
listener.recordDataOut("deleting completely", 1);
delegate.deleteCompletely();
}
public class ListeningAccessor implements StoreObjectAccessor {
protected final String path;
protected final StoreObjectAccessor delegate;
public ListeningAccessor(String path, StoreObjectAccessor delegate) {
this.path = path;
this.delegate = delegate;
}
@Override
public boolean exists() {
return delegate.exists();
}
@Override
public void put(String val) {
if (writesFailSilently)
return;
for (ObjectStoreTransactionListener listener: listeners)
listener.recordDataOut("writing "+path, val.length());
delegate.put(val);
}
@Override
public void append(String s) {
if (writesFailSilently)
return;
for (ObjectStoreTransactionListener listener: listeners)
listener.recordDataOut("appending "+path, s.length());
delegate.append(s);
}
@Override
public void delete() {
if (writesFailSilently)
return;
for (ObjectStoreTransactionListener listener: listeners)
listener.recordQueryOut("deleting "+path, path.length());
delegate.delete();
}
@Override
public String get() {
for (ObjectStoreTransactionListener listener: listeners)
listener.recordQueryOut("requesting "+path, path.length());
String result = delegate.get();
for (ObjectStoreTransactionListener listener: listeners)
listener.recordDataIn("reading "+path, (result==null ? 0 : result.length()));
return result;
}
@Override
public byte[] getBytes() {
return get().getBytes();
}
@Override
public Date getLastModifiedDate() {
return delegate.getLastModifiedDate();
}
}
public void setWritesFailSilently(boolean writesFailSilently) {
this.writesFailSilently = writesFailSilently;
}
}