001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.replication.protocol;
028    
029    import static org.opends.server.replication.protocol.OperationContext.*;
030    
031    import org.opends.server.core.ModifyOperationBasis;
032    import org.opends.server.protocols.asn1.ASN1Exception;
033    import org.opends.server.protocols.asn1.ASN1OctetString;
034    import org.opends.server.protocols.ldap.LDAPAttribute;
035    import org.opends.server.protocols.ldap.LDAPModification;
036    import org.opends.server.protocols.asn1.ASN1Element;
037    import org.opends.server.protocols.internal.InternalClientConnection;
038    import org.opends.server.replication.common.ChangeNumber;
039    import org.opends.server.replication.plugin.Historical;
040    import org.opends.server.types.AbstractOperation;
041    import org.opends.server.types.Attribute;
042    import org.opends.server.types.AttributeType;
043    import org.opends.server.types.AttributeUsage;
044    import org.opends.server.types.DN;
045    import org.opends.server.types.LDAPException;
046    import org.opends.server.types.Modification;
047    import org.opends.server.types.RawModification;
048    import org.opends.server.types.operation.PostOperationModifyOperation;
049    
050    
051    import java.io.UnsupportedEncodingException;
052    import java.util.ArrayList;
053    import java.util.List;
054    import java.util.zip.DataFormatException;
055    
056    /**
057     * Message used to send Modify information.
058     */
059    public class ModifyMsg extends UpdateMessage
060    {
061      private static final long serialVersionUID = -4905520652801395185L;
062      private byte[] encodedMods = null;
063      private byte[] encodedMsg = null;
064    
065      /**
066       * Creates a new Modify message from a ModifyOperation.
067       *
068       * @param op The operation to use for building the message
069       */
070      public ModifyMsg(PostOperationModifyOperation op)
071      {
072        super((OperationContext) op.getAttachment(OperationContext.SYNCHROCONTEXT),
073              op.getRawEntryDN().stringValue());
074        encodedMods = modsToByte(op.getModifications());
075      }
076    
077      /**
078       * Creates a new Modify message using the provided information.
079       *
080       * @param changeNumber The ChangeNumber for the operation.
081       * @param dn           The baseDN of the operation.
082       * @param mods         The mod of the operation.
083       * @param entryuuid    The unique id of the entry on which the modification
084       *                     needs to apply.
085       */
086      public ModifyMsg(ChangeNumber changeNumber, DN dn, List<Modification> mods,
087                       String entryuuid)
088      {
089        super(new ModifyContext(changeNumber, entryuuid),
090              dn.toNormalizedString());
091        this.encodedMods = modsToByte(mods);
092      }
093    
094      /**
095       * Creates a new Modify message from a byte[].
096       *
097       * @param in The byte[] from which the operation must be read.
098       * @throws DataFormatException If the input byte[] is not a valid modifyMsg
099       * @throws UnsupportedEncodingException If UTF8 is not supported by the JVM.
100       */
101      public ModifyMsg(byte[] in) throws DataFormatException,
102                                         UnsupportedEncodingException
103      {
104        super(in);
105        encodedMsg = in;
106      }
107    
108      /**
109       * Get the byte array representation of this Message.
110       *
111       * @return The byte array representation of this Message.
112       *
113       * @throws UnsupportedEncodingException  When the encoding of the message
114       *         failed because the UTF-8 encoding is not supported.
115       */
116      @Override
117      public byte[] getBytes() throws UnsupportedEncodingException
118      {
119        if (encodedMsg == null)
120        {
121          encode();
122        }
123        return encodedMsg;
124      }
125    
126      /**
127       * {@inheritDoc}
128       */
129      @Override
130      public AbstractOperation createOperation(InternalClientConnection connection,
131                       String newDn)
132                       throws LDAPException, ASN1Exception, DataFormatException
133      {
134        if (encodedMods == null)
135        {
136          decode();
137        }
138    
139        if (newDn == null)
140          newDn = getDn();
141    
142        ArrayList<RawModification> ldapmods;
143    
144        ArrayList<ASN1Element> mods = null;
145    
146        mods = ASN1Element.decodeElements(encodedMods);
147    
148        ldapmods = new ArrayList<RawModification>(mods.size());
149        for (ASN1Element elem : mods)
150          ldapmods.add(LDAPModification.decode(elem));
151    
152        ModifyOperationBasis mod = new ModifyOperationBasis(connection,
153                                   InternalClientConnection.nextOperationID(),
154                                   InternalClientConnection.nextMessageID(), null,
155                                   new ASN1OctetString(newDn), ldapmods);
156        ModifyContext ctx = new ModifyContext(getChangeNumber(), getUniqueId());
157        mod.setAttachment(SYNCHROCONTEXT, ctx);
158        return mod;
159      }
160    
161      /**
162       * Encode the Msg information into a byte array.
163       *
164       * @throws UnsupportedEncodingException If utf8 is not suported.
165       */
166      private void encode() throws UnsupportedEncodingException
167      {
168        /* encode the header in a byte[] large enough to also contain the mods */
169        encodedMsg = encodeHeader(MSG_TYPE_MODIFY_REQUEST, encodedMods.length + 1);
170        int pos = encodedMsg.length - (encodedMods.length + 1);
171    
172        /* add the mods */
173        pos = addByteArray(encodedMods, encodedMsg, pos);
174      }
175    
176      /**
177       * Decode the encodedMsg into mods and dn.
178       *
179       * @throws DataFormatException when the encodedMsg is no a valid modify.
180       */
181      private void decode() throws DataFormatException
182      {
183        int pos = decodeHeader(MSG_TYPE_MODIFY_REQUEST, encodedMsg);
184    
185        /* Read the mods : all the remaining bytes but the terminating 0 */
186        encodedMods = new byte[encodedMsg.length-pos-1];
187        int i =0;
188        while (pos<encodedMsg.length-1)
189        {
190          encodedMods[i++] = encodedMsg[pos++];
191        }
192      }
193    
194      /**
195       * Encode an ArrayList of Modification into a byte[] suitable
196       * for storage in a database or send on the network.
197       *
198       * @param mods the ArrayList of Modification to be encoded.
199       */
200      private byte[] modsToByte(List<Modification> mods)
201      {
202        ArrayList<ASN1Element> modsASN1;
203    
204        modsASN1 = new ArrayList<ASN1Element>(mods.size());
205        for (Modification mod : mods)
206        {
207          Attribute attr = mod.getAttribute();
208          AttributeType type = attr.getAttributeType();
209          if (type != null )
210          {
211            if (AttributeUsage.DSA_OPERATION.equals(type.getUsage()))
212            {
213              // Attributes with a dsaOperation usage should not be synchronized.
214              // skip them.
215              continue;
216            }
217          }
218    
219          if (!Historical.isHistoricalAttribute(attr))
220          {
221            LDAPModification ldapmod = new LDAPModification(
222              mod.getModificationType(), new LDAPAttribute(mod.getAttribute()));
223            modsASN1.add(ldapmod.encode());
224          }
225        }
226    
227        return ASN1Element.encodeValue(modsASN1);
228      }
229    
230      /**
231       * {@inheritDoc}
232       */
233      @Override
234      public String toString()
235      {
236        return("MOD " + getDn() + " " + getChangeNumber());
237      }
238    }