/* * 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.activemq.artemis.tests.unit.core.journal.impl.fakes; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.core.io.IOCallback; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.core.io.SequentialFileFactory; import org.apache.activemq.artemis.core.io.buffer.TimedBuffer; import org.apache.activemq.artemis.core.journal.EncodingSupport; public class FakeSequentialFileFactory implements SequentialFileFactory { private final Map<String, FakeSequentialFile> fileMap = new ConcurrentHashMap<>(); private volatile int alignment; private final boolean supportsCallback; private volatile boolean holdCallbacks; private ListenerHoldCallback holdCallbackListener; private volatile boolean generateErrors; private final List<CallbackRunnable> callbacksInHold; public FakeSequentialFileFactory(final int alignment, final boolean supportsCallback) { this.alignment = alignment; this.supportsCallback = supportsCallback; callbacksInHold = new ArrayList<>(); } public FakeSequentialFileFactory() { this(1, false); } @Override public SequentialFileFactory setDatasync(boolean enabled) { return null; } @Override public boolean isDatasync() { return false; } @Override public int getMaxIO() { return 1; } // Public -------------------------------------------------------- @Override public SequentialFile createSequentialFile(final String fileName) { FakeSequentialFile sf = fileMap.get(fileName); if (sf == null || sf.data == null) { sf = newSequentialFile(fileName); fileMap.put(fileName, sf); } else { sf.getData().position(0); // log.debug("positioning data to 0"); } return sf; } @Override public void clearBuffer(final ByteBuffer buffer) { final int limit = buffer.limit(); buffer.rewind(); for (int i = 0; i < limit; i++) { buffer.put((byte) 0); } buffer.rewind(); } @Override public List<String> listFiles(final String extension) { List<String> files = new ArrayList<>(); for (String s : fileMap.keySet()) { if (s.endsWith("." + extension)) { files.add(s); } } return files; } public Map<String, FakeSequentialFile> getFileMap() { return fileMap; } public void clear() { fileMap.clear(); } @Override public boolean isSupportsCallbacks() { return supportsCallback; } @Override public ByteBuffer newBuffer(int size) { if (size % alignment != 0) { size = (size / alignment + 1) * alignment; } return ByteBuffer.allocate(size); } @Override public int calculateBlockSize(final int position) { int alignment = getAlignment(); int pos = (position / alignment + (position % alignment != 0 ? 1 : 0)) * alignment; return pos; } @Override public ByteBuffer wrapBuffer(final byte[] bytes) { return ByteBuffer.wrap(bytes); } public synchronized boolean isHoldCallbacks() { return holdCallbacks; } public synchronized void setHoldCallbacks(final boolean holdCallbacks, final ListenerHoldCallback holdCallbackListener) { this.holdCallbacks = holdCallbacks; this.holdCallbackListener = holdCallbackListener; } public boolean isGenerateErrors() { return generateErrors; } public void setGenerateErrors(final boolean generateErrors) { this.generateErrors = generateErrors; } public synchronized void flushAllCallbacks() { for (Runnable action : callbacksInHold) { action.run(); } callbacksInHold.clear(); } public synchronized void flushCallback(final int position) { Runnable run = callbacksInHold.get(position); run.run(); callbacksInHold.remove(run); } public synchronized void setCallbackAsError(final int position) { callbacksInHold.get(position).setSendError(true); } public synchronized int getNumberOfCallbacks() { return callbacksInHold.size(); } @Override public int getAlignment() { return alignment; } @Override public FakeSequentialFileFactory setAlignment(int alignment) { this.alignment = alignment; return this; } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- protected FakeSequentialFile newSequentialFile(final String fileName) { return new FakeSequentialFile(fileName); } // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- /** * This listener will return a message to the test with each callback added */ public interface ListenerHoldCallback { void callbackAdded(final ByteBuffer bytes); } private class CallbackRunnable implements Runnable { final FakeSequentialFile file; final ByteBuffer bytes; final IOCallback callback; volatile boolean sendError; CallbackRunnable(final FakeSequentialFile file, final ByteBuffer bytes, final IOCallback callback) { this.file = file; this.bytes = bytes; this.callback = callback; } @Override public void run() { if (sendError) { callback.onError(ActiveMQExceptionType.UNSUPPORTED_PACKET.getCode(), "Fake aio error"); } else { try { file.data.put(bytes); if (callback != null) { callback.done(); } } catch (Throwable e) { e.printStackTrace(); callback.onError(ActiveMQExceptionType.GENERIC_EXCEPTION.getCode(), e.getMessage()); } } } public void setSendError(final boolean sendError) { this.sendError = sendError; } } public class FakeSequentialFile implements SequentialFile { private volatile boolean open; private String fileName; private ByteBuffer data; public ByteBuffer getData() { return data; } @Override public boolean isOpen() { return open; } public void flush() { } public FakeSequentialFile(final String fileName) { this.fileName = fileName; } @Override public synchronized void close() { open = false; if (data != null) { data.position(0); } notifyAll(); } @Override public void delete() { if (open) { close(); } fileMap.remove(fileName); } @Override public String getFileName() { return fileName; } @Override public void open() throws Exception { open(1, true); } @Override public synchronized void open(final int currentMaxIO, final boolean useExecutor) throws Exception { open = true; checkAndResize(0); } @Override public void fill(final int size) throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } checkAndResize(size); // log.debug("size is " + size + " pos is " + pos); for (int i = 0; i < size; i++) { byte[] array = data.array(); array[i] = 0; // log.debug("Filling " + pos + " with char " + fillCharacter); } } @Override public int read(final ByteBuffer bytes) throws Exception { return read(bytes, null); } @Override public int read(final ByteBuffer bytes, final IOCallback callback) throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } byte[] bytesRead = new byte[bytes.limit()]; data.get(bytesRead); bytes.put(bytesRead); bytes.rewind(); if (callback != null) { callback.done(); } return bytesRead.length; } @Override public void position(final long pos) { if (!open) { throw new IllegalStateException("Is closed"); } checkAlignment(pos); data.position((int) pos); } @Override public long position() { return data.position(); } @Override public synchronized void writeDirect(final ByteBuffer bytes, final boolean sync, final IOCallback callback) { if (!open) { throw new IllegalStateException("Is closed"); } final int position = data == null ? 0 : data.position(); // checkAlignment(position); // checkAlignment(bytes.limit()); checkAndResize(bytes.limit() + position); CallbackRunnable action = new CallbackRunnable(this, bytes, callback); if (generateErrors) { action.setSendError(true); } if (holdCallbacks) { addCallback(bytes, action); } else { action.run(); } } @Override public void sync() throws IOException { if (supportsCallback) { throw new IllegalStateException("sync is not supported when supportsCallback=true"); } } @Override public long size() throws Exception { if (data == null) { return 0; } else { return data.limit(); } } @Override public void writeDirect(final ByteBuffer bytes, final boolean sync) throws Exception { writeDirect(bytes, sync, null); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#writeInternal(java.nio.ByteBuffer) */ public void writeInternal(ByteBuffer bytes) throws Exception { writeDirect(bytes, true); } private void checkAndResize(final int size) { int oldpos = data == null ? 0 : data.position(); if (data == null || data.array().length < size) { byte[] newBytes = new byte[size]; if (data != null) { System.arraycopy(data.array(), 0, newBytes, 0, data.array().length); } data = ByteBuffer.wrap(newBytes); data.position(oldpos); } } /** * @param bytes * @param action */ private void addCallback(final ByteBuffer bytes, final CallbackRunnable action) { synchronized (FakeSequentialFileFactory.this) { callbacksInHold.add(action); if (holdCallbackListener != null) { holdCallbackListener.callbackAdded(bytes); } } } @Override public int calculateBlockStart(final int position) throws Exception { int pos = (position / alignment + (position % alignment != 0 ? 1 : 0)) * alignment; return pos; } @Override public String toString() { return "FakeSequentialFile:" + fileName; } private void checkAlignment(final long position) { if (position % alignment != 0) { throw new IllegalStateException("Position is not aligned to " + alignment); } } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#renameTo(org.apache.activemq.artemis.core.io.SequentialFile) */ @Override public void renameTo(final String newFileName) throws Exception { fileMap.remove(fileName); fileName = newFileName; fileMap.put(newFileName, this); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#fits(int) */ @Override public boolean fits(final int size) { return data.position() + size <= data.limit(); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#setBuffering(boolean) */ public void setBuffering(final boolean buffering) { } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#lockBuffer() */ public void disableAutoFlush() { } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#unlockBuffer() */ public void enableAutoFlush() { } @Override public SequentialFile cloneFile() { return null; // To change body of implemented methods use File | Settings | File Templates. } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#write(org.apache.activemq.artemis.spi.core.remoting.ActiveMQBuffer, boolean, org.apache.activemq.artemis.core.journal.IOCallback) */ @Override public void write(final ActiveMQBuffer bytes, final boolean sync, final IOCallback callback) throws Exception { bytes.writerIndex(bytes.capacity()); bytes.readerIndex(0); writeDirect(bytes.toByteBuffer(), sync, callback); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#write(org.apache.activemq.artemis.spi.core.remoting.ActiveMQBuffer, boolean) */ @Override public void write(final ActiveMQBuffer bytes, final boolean sync) throws Exception { bytes.writerIndex(bytes.capacity()); bytes.readerIndex(0); writeDirect(bytes.toByteBuffer(), sync); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#write(org.apache.activemq.artemis.core.journal.EncodingSupport, boolean, org.apache.activemq.artemis.core.journal.IOCompletion) */ @Override public void write(final EncodingSupport bytes, final boolean sync, final IOCallback callback) throws Exception { ByteBuffer buffer = newBuffer(bytes.getEncodeSize()); ActiveMQBuffer outbuffer = ActiveMQBuffers.wrappedBuffer(buffer); bytes.encode(outbuffer); write(outbuffer, sync, callback); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#write(org.apache.activemq.artemis.core.journal.EncodingSupport, boolean) */ @Override public void write(final EncodingSupport bytes, final boolean sync) throws Exception { ByteBuffer buffer = newBuffer(bytes.getEncodeSize()); ActiveMQBuffer outbuffer = ActiveMQBuffers.wrappedBuffer(buffer); bytes.encode(outbuffer); write(outbuffer, sync); } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#exists() */ @Override public boolean exists() { FakeSequentialFile file = fileMap.get(fileName); return file != null && file.data != null && file.data.capacity() > 0; } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#setTimedBuffer(org.apache.activemq.artemis.core.io.buffer.TimedBuffer) */ @Override public void setTimedBuffer(final TimedBuffer buffer) { } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.io.SequentialFile#copyTo(org.apache.activemq.artemis.core.io.SequentialFile) */ @Override public void copyTo(SequentialFile newFileName) { // TODO Auto-generated method stub } @Override public File getJavaFile() { throw new UnsupportedOperationException(); } } @Override public void createDirs() throws Exception { // nothing to be done on the fake Sequential file } @Override public void releaseBuffer(final ByteBuffer buffer) { } @Override public void stop() { } @Override public void activateBuffer(final SequentialFile file) { } @Override public void start() { } @Override public void deactivateBuffer() { } @Override public void flush() { } @Override public void onIOError(Exception exception, String message, SequentialFile file) { } @Override public ByteBuffer allocateDirectBuffer(int size) { return ByteBuffer.allocateDirect(size); } @Override public void releaseDirectBuffer(ByteBuffer buffer) { } @Override public File getDirectory() { // TODO Auto-generated method stub return null; } }