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 }