/* * 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.core.persistence.impl.journal; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.core.message.LargeBodyEncoder; import org.apache.activemq.artemis.core.message.impl.CoreMessage; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.LargeServerMessage; import org.apache.activemq.artemis.utils.DataConstants; import org.apache.activemq.artemis.utils.collections.TypedProperties; import org.jboss.logging.Logger; public final class LargeServerMessageImpl extends CoreMessage implements LargeServerMessage { // Constants ----------------------------------------------------- private static final Logger logger = Logger.getLogger(LargeServerMessageImpl.class); // Attributes ---------------------------------------------------- private final JournalStorageManager storageManager; private long pendingRecordID = -1; private boolean paged; // We should only use the NIO implementation on the Journal private SequentialFile file; // set when a copyFrom is called // The actual copy is done when finishCopy is called private SequentialFile pendingCopy; private long bodySize = -1; private final AtomicInteger delayDeletionCount = new AtomicInteger(0); // We cache this private volatile int memoryEstimate = -1; public LargeServerMessageImpl(final JournalStorageManager storageManager) { this.storageManager = storageManager; } /** * Copy constructor * * @param properties * @param copy * @param fileCopy */ private LargeServerMessageImpl(final LargeServerMessageImpl copy, TypedProperties properties, final SequentialFile fileCopy, final long newID) { super(copy, properties); storageManager = copy.storageManager; file = fileCopy; bodySize = copy.bodySize; setMessageID(newID); } private static String toDate(long timestamp) { if (timestamp == 0) { return "0"; } else { return new java.util.Date(timestamp).toString(); } } @Override public boolean isServerMessage() { return true; } @Override public long getPendingRecordID() { return this.pendingRecordID; } /** * @param pendingRecordID */ @Override public void setPendingRecordID(long pendingRecordID) { this.pendingRecordID = pendingRecordID; } @Override public void setPaged() { paged = true; } @Override public synchronized void addBytes(final byte[] bytes) throws Exception { validateFile(); if (!file.isOpen()) { file.open(); } storageManager.addBytesToLargeMessage(file, getMessageID(), bytes); bodySize += bytes.length; } @Override public synchronized int getEncodeSize() { return getHeadersAndPropertiesEncodeSize(); } public void encode(final ActiveMQBuffer buffer1) { super.encodeHeadersAndProperties(buffer1.byteBuf()); } public void decode(final ActiveMQBuffer buffer1) { file = null; super.decodeHeadersAndProperties(buffer1.byteBuf()); } @Override public synchronized void incrementDelayDeletionCount() { delayDeletionCount.incrementAndGet(); try { incrementRefCount(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.errorIncrementDelayDeletionCount(e); } } @Override public synchronized void decrementDelayDeletionCount() throws Exception { int count = delayDeletionCount.decrementAndGet(); decrementRefCount(); if (count == 0) { checkDelete(); } } @Override public LargeBodyEncoder getBodyEncoder() throws ActiveMQException { validateFile(); return new DecodingContext(); } private void checkDelete() throws Exception { if (getRefCount() <= 0) { if (logger.isTraceEnabled()) { logger.trace("Deleting file " + file + " as the usage was complete"); } try { deleteFile(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.error(e.getMessage(), e); } } } @Override public synchronized int decrementRefCount() throws Exception { int currentRefCount = super.decrementRefCount(); // We use <= as this could be used by load. // because of a failure, no references were loaded, so we have 0... and we still need to delete the associated // files if (delayDeletionCount.get() <= 0) { checkDelete(); } return currentRefCount; } @Override public boolean isLargeMessage() { return true; } @Override public synchronized void deleteFile() throws Exception { validateFile(); releaseResources(); storageManager.deleteLargeMessageFile(this); } @Override public synchronized int getMemoryEstimate() { if (memoryEstimate == -1) { // The body won't be on memory (aways on-file), so we don't consider this for paging memoryEstimate = getHeadersAndPropertiesEncodeSize() + DataConstants.SIZE_INT + getEncodeSize() + (16 + 4) * 2 + 1; } return memoryEstimate; } @Override public synchronized void releaseResources() { if (file != null && file.isOpen()) { try { file.close(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.largeMessageErrorReleasingResources(e); } } } @Override public void referenceOriginalMessage(final Message original, String originalQueue) { super.referenceOriginalMessage(original, originalQueue); if (original instanceof LargeServerMessageImpl) { LargeServerMessageImpl otherLM = (LargeServerMessageImpl) original; this.paged = otherLM.paged; if (this.paged) { this.removeAnnotation(Message.HDR_ORIG_MESSAGE_ID); } } } @Override public Message copy() { SequentialFile newfile = storageManager.createFileForLargeMessage(messageID, durable); Message newMessage = new LargeServerMessageImpl(this, properties, newfile, messageID); return newMessage; } @Override public Message copy(final long newID) { try { LargeServerMessage newMessage = storageManager.createLargeMessage(newID, this); boolean originallyOpen = file != null && file.isOpen(); validateFile(); byte[] bufferBytes = new byte[100 * 1024]; ByteBuffer buffer = ByteBuffer.wrap(bufferBytes); long oldPosition = file.position(); file.open(); file.position(0); for (;;) { // The buffer is reused... // We need to make sure we clear the limits and the buffer before reusing it buffer.clear(); int bytesRead = file.read(buffer); byte[] bufferToWrite; if (bytesRead <= 0) { break; } else if (bytesRead == bufferBytes.length) { bufferToWrite = bufferBytes; } else { bufferToWrite = new byte[bytesRead]; System.arraycopy(bufferBytes, 0, bufferToWrite, 0, bytesRead); } newMessage.addBytes(bufferToWrite); if (bytesRead < bufferBytes.length) { break; } } file.position(oldPosition); if (!originallyOpen) { file.close(); newMessage.getFile().close(); } return newMessage; } catch (Exception e) { ActiveMQServerLogger.LOGGER.lareMessageErrorCopying(e, this); return null; } } @Override public SequentialFile getFile() throws ActiveMQException { validateFile(); return file; } @Override public String toString() { return "LargeServerMessage[messageID=" + messageID + ",durable=" + isDurable() + ",userID=" + getUserID() + ",priority=" + this.getPriority() + ", timestamp=" + toDate(getTimestamp()) + ",expiration=" + toDate(getExpiration()) + ", durable=" + durable + ", address=" + getAddress() + ",properties=" + (properties != null ? properties.toString() : "") + "]@" + System.identityHashCode(this); } @Override protected void finalize() throws Throwable { releaseResources(); super.finalize(); } // Private ------------------------------------------------------- public synchronized void validateFile() throws ActiveMQException { try { if (file == null) { if (messageID <= 0) { throw new RuntimeException("MessageID not set on LargeMessage"); } file = createFile(); openFile(); bodySize = file.size(); } } catch (Exception e) { // TODO: There is an IO_ERROR on trunk now, this should be used here instead throw new ActiveMQInternalErrorException(e.getMessage(), e); } } /** * */ protected SequentialFile createFile() { return storageManager.createFileForLargeMessage(getMessageID(), durable); } protected void openFile() throws Exception { if (file == null) { validateFile(); } else if (!file.isOpen()) { file.open(); } } protected void closeFile() throws Exception { if (file != null && file.isOpen()) { file.close(); } } // Inner classes ------------------------------------------------- class DecodingContext implements LargeBodyEncoder { private SequentialFile cFile; @Override public void open() throws ActiveMQException { try { if (cFile != null && cFile.isOpen()) { cFile.close(); } cFile = file.cloneFile(); cFile.open(); } catch (Exception e) { throw new ActiveMQException(ActiveMQExceptionType.INTERNAL_ERROR, e.getMessage(), e); } } @Override public void close() throws ActiveMQException { try { if (cFile != null) { cFile.close(); } } catch (Exception e) { throw new ActiveMQInternalErrorException(e.getMessage(), e); } } @Override public int encode(final ByteBuffer bufferRead) throws ActiveMQException { try { return cFile.read(bufferRead); } catch (Exception e) { throw new ActiveMQInternalErrorException(e.getMessage(), e); } } @Override public int encode(final ActiveMQBuffer bufferOut, final int size) throws ActiveMQException { // This could maybe be optimized (maybe reading directly into bufferOut) ByteBuffer bufferRead = ByteBuffer.allocate(size); int bytesRead = encode(bufferRead); bufferRead.flip(); if (bytesRead > 0) { bufferOut.writeBytes(bufferRead.array(), 0, bytesRead); } return bytesRead; } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.message.LargeBodyEncoder#getLargeBodySize() */ @Override public long getLargeBodySize() { if (bodySize < 0) { try { bodySize = file.size(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e); } } return bodySize; } } }