001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023package dk.netarkivet.common.distribute;
024
025import java.io.ObjectInputStream;
026import java.io.ObjectOutputStream;
027import java.io.Serializable;
028
029import dk.netarkivet.common.exceptions.ArgumentNotValid;
030import dk.netarkivet.common.exceptions.IOFailure;
031import dk.netarkivet.common.exceptions.PermissionDenied;
032import dk.netarkivet.common.utils.ExceptionUtils;
033
034/**
035 * Common base class for all messages exchanged in the NetarchiveSuite.
036 */
037@SuppressWarnings({"serial"})
038public abstract class NetarkivetMessage implements Serializable {
039
040    // contains the error messages when isOk=false
041    private String errMsg;
042
043    // when false an error occurred processing the message
044    private boolean isOk = true;
045
046    // id of this message. Is set when sent and null until then
047    private String id;
048
049    // Channel to which this message is to be sent
050    private ChannelID to;
051
052    // Channel on which replies are expected
053    private ChannelID replyTo;
054
055    // a delimiter to separate error messages
056    static final String ERROR_DELIMITTER = "\n-----------------\n";
057
058    protected String replyOfId;
059
060    /**
061     * Creates a new NetarkivetMessage.
062     *
063     * @param to the initial receiver of the message
064     * @param replyTo the initial sender of the message
065     * @throws ArgumentNotValid if to==replyTo, the replyTo parameter is a topic instead of a queue, or there is a null
066     * parameter.
067     */
068    protected NetarkivetMessage(ChannelID to, ChannelID replyTo) {
069        ArgumentNotValid.checkNotNull(to, "to");
070        ArgumentNotValid.checkNotNull(replyTo, "replyTo");
071
072        if (to.getName().equals(replyTo.getName())) {
073            throw new ArgumentNotValid("to and replyTo should not be equal.");
074        }
075
076        // Have not implemented replying to a topic because there is no use
077        // for it in our current architecture
078        if (Channels.isTopic(replyTo.getName())) {
079            throw new ArgumentNotValid("Reply channel must be queue but " + replyTo.toString() + " is a Topic");
080        }
081
082        this.to = to;
083        this.replyTo = replyTo;
084        this.id = null;
085        this.replyOfId = null;
086    }
087
088    /**
089     * Did an error occur when processing the message.
090     *
091     * @return true if no error occurred, otherwise false
092     */
093    public boolean isOk() {
094        return isOk;
095    }
096
097    /**
098     * Set or append error message. Sets isOk field to false.
099     *
100     * @param err error message
101     */
102    public void setNotOk(String err) {
103        if (isOk) {
104            errMsg = err;
105            this.isOk = false;
106        } else {
107            errMsg += ERROR_DELIMITTER;
108            errMsg += err;
109        }
110    }
111
112    /**
113     * Set error message based on an exception.
114     *
115     * @param e An exception thrown during processing.
116     */
117    public void setNotOk(Throwable e) {
118        setNotOk(e.toString() + "\n" + ExceptionUtils.getStackTrace(e));
119    }
120
121    /**
122     * Retrieve error message.
123     *
124     * @return error message
125     * @throws PermissionDenied if the message is not an error message
126     */
127    public String getErrMsg() throws PermissionDenied {
128        if (isOk) {
129            throw new PermissionDenied("Can't get error message for message '" + this + " that has had no error");
130        }
131        return errMsg;
132    }
133
134    /**
135     * Retrieve message id. Note that message ID is not set until message is sent, and this method must not be called
136     * before then.
137     *
138     * @return message id
139     * @throws PermissionDenied If the message has not yet been sent.
140     */
141    public synchronized String getID() {
142        if (id == null) {
143            throw new PermissionDenied("This message has not been sent, and does not yet have an ID");
144        }
145        return id;
146    }
147
148    /**
149     * Sets the ID of this message if it has not already been set.
150     *
151     * @param newId The new ID
152     */
153    synchronized void updateId(String newId) {
154        if (this.id == null) {
155            this.id = newId;
156        }
157        if (this.replyOfId == null) {
158            this.replyOfId = newId;
159        }
160    }
161
162    /**
163     * Retrieve replyOfId. This is set by subclasses of NetarkivetMessage, to indicate that this is a reply of some
164     * other message. If the subclass doesn't set replyOfId, this method behaves like getId.
165     *
166     * @return replyOfId
167     */
168    public synchronized String getReplyOfId() {
169        if (replyOfId != null) {
170            return replyOfId;
171        } else {
172            return getID();
173        }
174    }
175
176    /**
177     * Retrieve initial destination.
178     *
179     * @return initial destination
180     */
181    public ChannelID getTo() {
182        return to;
183    }
184
185    /**
186     * Retrieve specified reply channel.
187     *
188     * @return initial origin
189     */
190    public ChannelID getReplyTo() {
191        return replyTo;
192    }
193
194    /**
195     * Returns a string containing: <id>: To <toName> ReplyTo <replyToName> <isOK> [:error message].
196     *
197     * @return String representation of Message.
198     */
199    public String toString() {
200        String s = (id == null ? "NO ID" : id);
201        s += ": To " + to.getName() + " ReplyTo " + replyTo.getName();
202        s += isOk() ? " OK" : " Error: " + errMsg;
203        return s;
204    }
205
206    /**
207     * Invoke default method for deserializing object.
208     *
209     * @param s The stream the object is read from.
210     */
211    private void readObject(ObjectInputStream s) {
212        try {
213            s.defaultReadObject();
214        } catch (Exception e) {
215            throw new IOFailure("Unexpected error during deserialization", e);
216        }
217    }
218
219    /**
220     * Invoke default method for serializing object.
221     *
222     * @param s The stream the object is written to.
223     */
224    private void writeObject(ObjectOutputStream s) {
225        try {
226            s.defaultWriteObject();
227        } catch (Exception e) {
228            throw new IOFailure("Unexpected error during serialization", e);
229        }
230    }
231
232    /**
233     * Check, if a given message has been sent yet. If the message has a null id, it hasn't been sent yet.
234     *
235     * @return true, if message has been sent yet, false otherwise.
236     */
237    public synchronized boolean hasBeenSent() {
238        return (this.id != null);
239    }
240
241}