Java tutorial
/* * Copyright 2015 Adaptris Ltd. * * Licensed 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 com.adaptris.core; import static com.adaptris.core.metadata.MetadataResolver.resolveKey; import static org.apache.commons.lang.StringUtils.isEmpty; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.adaptris.core.util.Args; import com.adaptris.util.IdGenerator; import com.adaptris.util.stream.StreamUtil; /** * <p> * Standard implementation of {@link com.adaptris.core.AdaptrisMessage} interface. * </p> * <p> * When referring to metadata items by key you can use a form of indirection by specifying a prefix to your key ("$$").<br /> * Using this prefix on any metadata key will not perform an action (retrieve, add etc) to that metadata item but instead will look * up the value of that metadata key and use the value as the metadata item key to be performed on. <br /> * Example: <br /> * Calling <b>addMetadata("myKey", "myValue");</b><br /> * Will create a new metadata item with the key "myKey" and the value "myValue".<br /> * However calling <b>addMetadata("$$myKey", "myValue")</b> will lookup the value of the metadata key named "myKey" and use that * value as the key for the addMetadata() method. * </p> * * * @see DefaultMessageFactory * @see AdaptrisMessageFactory * @see AdaptrisMessage * @author hfraser * @author $Author: lchan $ */ public abstract class AdaptrisMessageImp implements AdaptrisMessage, Cloneable { // If we have %message{key1}%message{key2} group(1) is key2 // Which is then replaced so it all works out int the end. private static final String RESOLVE_REGEXP = "^.*%message\\{([\\w!\\$\"#&%'\\*\\+,\\-\\.:=]+)\\}.*$"; private transient Logger log = LoggerFactory.getLogger(AdaptrisMessage.class); private transient Pattern normalResolver = Pattern.compile(RESOLVE_REGEXP); private transient Pattern dotAllResolver = Pattern.compile(RESOLVE_REGEXP, Pattern.DOTALL); private IdGenerator guidGenerator; // persistent fields private String uniqueId; private Set<MetadataElement> metadata; private String contentEncoding; // in memory only e.g. lost on send or persist private MessageLifecycleEvent messageLifeCycle; private Map<Object, Object> objectMetadata; private String nextServiceId; private AdaptrisMessageFactory factory; private enum Resolvers { UniqueId { @Override String resolve(String key, AdaptrisMessage msg) { if ("%uniqueId".equalsIgnoreCase(key)) { return msg.getUniqueId(); } return null; } }, Size { @Override String resolve(String key, AdaptrisMessage msg) { if ("%size".equalsIgnoreCase(key)) { return String.valueOf(msg.getSize()); } return null; } }, Metadata { @Override String resolve(String key, AdaptrisMessage msg) { return msg.getMetadataValue(key); } }; abstract String resolve(String key, AdaptrisMessage msg); } private AdaptrisMessageImp() { } /** * <p> * Creates a new instance. * </p> * * @param guid a GuidGenerator that will be used to create unique-ids. */ protected AdaptrisMessageImp(IdGenerator guid, AdaptrisMessageFactory fac) { this(); factory = fac; metadata = new HashSet<MetadataElement>(); objectMetadata = new HashMap<>(); guidGenerator = guid; messageLifeCycle = new MessageLifecycleEvent(); messageLifeCycle.setCreationTime(System.currentTimeMillis()); messageLifeCycle.setUniqueId(guidGenerator.create(messageLifeCycle)); setUniqueId(guidGenerator.create(this)); // setPayload(new byte[0]); nextServiceId = ""; } @Override public AdaptrisMessageFactory getFactory() { return factory; } @Override public void setCharEncoding(String charEnc) { contentEncoding = charEnc; } @Override public String getCharEncoding() { return contentEncoding; } /** @see AdaptrisMessage#setContentEncoding(String) */ @Override public void setContentEncoding(String charEnc) { contentEncoding = charEnc; } /** @see AdaptrisMessage#getContentEncoding() */ @Override public String getContentEncoding() { return contentEncoding; } @Override public boolean containsKey(String key) { return metadata.contains(new MetadataElement(resolveKey(this, key), "")); } /** @see AdaptrisMessage#headersContainsKey(String) */ @Override public boolean headersContainsKey(String key) { return metadata.contains(new MetadataElement(resolveKey(this, key), "")); } @Override public synchronized void addMetadata(String key, String value) { this.addMessageHeader(resolveKey(this, key), value); } @Override public void addMessageHeader(String key, String value) { this.addMetadata(new MetadataElement(resolveKey(this, key), value)); } /** @see AdaptrisMessage#addMetadata(MetadataElement) */ @Override public synchronized void addMetadata(MetadataElement e) { e.setKey(resolveKey(this, e.getKey())); if (metadata.contains(e)) { removeMetadata(e); } metadata.add(e); } /** @see AdaptrisMessage#removeMetadata(MetadataElement) */ @Override public void removeMetadata(MetadataElement element) { element.setKey(resolveKey(this, element.getKey())); metadata.remove(element); } /** @see AdaptrisMessage#removeMessageHeader(String) */ @Override public void removeMessageHeader(String key) { metadata.remove(new MetadataElement(resolveKey(this, key), "")); } @Override public synchronized void setMetadata(Set<MetadataElement> set) { if (set != null) { for (MetadataElement e : set) { addMetadata(e); } } } @Override public void setMessageHeaders(Map<String, String> metadata) { for (Map.Entry<String, String> entry : metadata.entrySet()) { this.addMessageHeader(entry.getKey(), entry.getValue()); } } /** @see AdaptrisMessage#clearMetadata() */ @Override public synchronized void clearMetadata() { metadata = new HashSet<MetadataElement>(); } /** @see AdaptrisMessage#getMetadataValue(String) */ @Override public String getMetadataValue(String key) { // is case-sensitive if (key != null) { return getValue(resolveKey(this, key)); } return null; } /** @see AdaptrisMessage#getMetadata(String) */ @Override public MetadataElement getMetadata(String key) { // lgtm [java/unsynchronized-getter] String resolved = resolveKey(this, key); if (key != null && containsKey(resolved)) { return new MetadataElement(resolved, getValue(resolved)); } return null; } @Override public Map<String, String> getMessageHeaders() { Map<String, String> newSet = new HashMap<String, String>(); for (MetadataElement kp : metadata) { newSet.put(kp.getKey(), kp.getValue()); } return newSet; } @Override public Set<MetadataElement> getMetadata() { // lgtm [java/unsynchronized-getter] return new HashSet<MetadataElement>(metadata); } @Override public String getUniqueId() { return uniqueId; } @Override public void setUniqueId(String s) { uniqueId = s; messageLifeCycle.setMessageUniqueId(getUniqueId()); } @Override public Reader getReader() throws IOException { return getContentEncoding() != null ? new InputStreamReader(getInputStream(), getContentEncoding()) : new InputStreamReader(getInputStream()); } @Override public Writer getWriter() throws IOException { return getContentEncoding() != null ? new OutputStreamWriter(getOutputStream(), getContentEncoding()) : new OutputStreamWriter(getOutputStream()); } @Override public Writer getWriter(String encoding) throws IOException { return !isEmpty(encoding) ? new ContentEncodingOnClose(getOutputStream(), encoding) : getWriter(); } @Override public void addEvent(MessageEventGenerator meg, boolean wasSuccessful) { String confirmationId = (String) getObjectHeaders().get(MessageEventGenerator.CONFIRMATION_ID_KEY); if (meg == null) { messageLifeCycle.addMleMarker(getNextMleMarker(new MessageEventGenerator() { @Override public String createName() { return "Unknown Event"; } @Override public String createQualifier() { return ""; } @Override public boolean isTrackingEndpoint() { return false; } @Override public boolean isConfirmation() { return false; } }, wasSuccessful, confirmationId)); } else { messageLifeCycle.addMleMarker(getNextMleMarker(meg, wasSuccessful, confirmationId)); } } /** @see AdaptrisMessage#getMessageLifecycleEvent() */ @Override public MessageLifecycleEvent getMessageLifecycleEvent() { return messageLifeCycle; } /** @see AdaptrisMessage#encode(AdaptrisMessageEncoder) */ @Override public byte[] encode(AdaptrisMessageEncoder encoder) throws CoreException { if (encoder != null) { ByteArrayOutputStream out = new ByteArrayOutputStream(); encoder.writeMessage(this, out); return out.toByteArray(); } return getPayload(); } @Override public void addObjectMetadata(String key, Object object) { objectMetadata.put(key, object); } /** * <p> * Adds an <code>Object</code> to this message as metadata. Object metadata is intended to be used within a single * <code>Workflow</code> only and will not be encoded or otherwise transported between Workflows. * </p> * * @param object the <code>Object</code> to set as metadata * @param key the key to store this object against. */ public void addObjectHeader(Object key, Object object) { objectMetadata.put(key, object); } @Override public Map getObjectMetadata() { return objectMetadata; } @Override public Map<Object, Object> getObjectHeaders() { return objectMetadata; } /** @see Object#toString() */ @Override public String toString() { return this.toString(false); } @Override public String toString(boolean includePayload, boolean includeEvents) { ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("uniqueId", getUniqueId()).append("metadata", metadata); if (includeEvents) { builder.append("message events", messageLifeCycle); } if (includePayload) { builder.append("payload", getPayloadForLogging()); } return builder.toString(); } /** @see AdaptrisMessage#toString(boolean) */ @Override public String toString(boolean extended) { return toString(extended, false); } /** @see AdaptrisMessage#getNextServiceId() */ @Override public String getNextServiceId() { return nextServiceId; } @Override public void setNextServiceId(String s) { nextServiceId = Args.notNull(s, nextServiceId); } @Override public String resolve(String s, boolean dotAll) { return resolve(s, dotAll ? dotAllResolver : normalResolver); } private String resolve(String s, Pattern pattern) { if (s == null) { return null; } String result = s; Matcher m = pattern.matcher(s); while (m.matches()) { String key = m.group(1); String metadataValue = internalResolve(key); // Optional<String> metadataValue = (Optional<String>) Optional.ofNullable(internalResolve(key)); if (metadataValue == null) { throw new UnresolvedMetadataException("Could not resolve [" + key + "] as metadata/uniqueId/size"); } String toReplace = "%message{" + key + "}"; result = result.replace(toReplace, metadataValue); // result = result.replace(toReplace, metadataValue // .orElseThrow(() -> new UnresolvedMetadataException("Could not resolve [" + key + "] as metadata/uniqueId/size"))); m = pattern.matcher(result); } return result; } private String internalResolve(String key) { String value = null; for (Resolvers r : Resolvers.values()) { value = r.resolve(key, this); if (value != null) { break; } } return value; } private MleMarker getNextMleMarker(MessageEventGenerator meg, boolean successful, String confId) { long seq = nextSequenceNumber(); this.addMetadata(CoreConstants.MLE_SEQUENCE_KEY, String.valueOf(seq)); MleMarker mleMarker = new MleMarker(meg, successful, seq, guidGenerator.create(meg), confId); return mleMarker; } private long nextSequenceNumber() { int result = 0; if (containsKey(CoreConstants.MLE_SEQUENCE_KEY)) { try { result = Integer.parseInt(getMetadataValue(CoreConstants.MLE_SEQUENCE_KEY)); } catch (Exception e) { log.warn("Failed to assign next lifecycle marker number, resetting"); result = 0; } result++; } // This s somewhat a fudge, because, // * The MessageLifecycleEvent is sent after the producer has produced // the message. // * The Producer adds a ProduceSuccess marker event after the payload has // been produced, // This means that the message, at the time it is produced, is always 1 // step out of sync, use the firstMleMarker to fix this state of affairs // when we read the message back. if (messageLifeCycle.getMleMarkers().size() == 0) { result++; } return result; } /** * @see com.adaptris.core.AdaptrisMessage #getMetadataValueIgnoreKeyCase(java.lang.String) */ @Override public String getMetadataValueIgnoreKeyCase(String key) { String result = getMetadataValue(key); if (result == null) { String resolvedKey = resolveKey(this, key); for (MetadataElement e : metadata) { if (e.getKey().equalsIgnoreCase(resolvedKey)) { result = e.getValue(); break; } } } return result; } /** @see Object#clone() */ @Override public Object clone() throws CloneNotSupportedException { AdaptrisMessage result = (AdaptrisMessage) super.clone(); result.clearMetadata(); result.setMetadata(cloneMetadata()); MessageLifecycleEvent copy = getMessageLifecycleEvent().clone(); ((AdaptrisMessageImp) result).messageLifeCycle = copy; Map objMdCopy = new HashMap(); objMdCopy.putAll(getObjectHeaders()); ((AdaptrisMessageImp) result).objectMetadata = objMdCopy; return result; } private Set<MetadataElement> cloneMetadata() throws CloneNotSupportedException { Set<MetadataElement> metadata = getMetadata(); Set<MetadataElement> result = new HashSet<MetadataElement>(); for (MetadataElement m : metadata) { result.add((MetadataElement) m.clone()); } return result; } /** * Copy the payload from one AdaptrisMessage to another. * * @param src the source adaptris message * @param dest the destination adaptris message * @throws IOException on exception */ public static void copyPayload(AdaptrisMessage src, AdaptrisMessage dest) throws IOException { StreamUtil.copyAndClose(src.getInputStream(), dest.getOutputStream()); } @Deprecated protected static boolean areEqual(String s1, String s2) { return StringUtils.equals(s1, s2); } private String getValue(String key) { for (MetadataElement e : metadata) { if (e.getKey().equals(key)) { return e.getValue(); } } return null; } private class ContentEncodingOnClose extends OutputStreamWriter { private String charset; public ContentEncodingOnClose(OutputStream out, String charsetName) throws UnsupportedEncodingException { super(out, charsetName); this.charset = charsetName; } public void close() throws IOException { super.close(); setContentEncoding(charset); } } }