001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.server.core.partition.impl.btree.mavibot;
021
022
023import java.io.ByteArrayInputStream;
024import java.io.ByteArrayOutputStream;
025import java.io.IOException;
026import java.io.ObjectInputStream;
027import java.io.ObjectOutput;
028import java.io.ObjectOutputStream;
029import java.nio.ByteBuffer;
030import java.util.Comparator;
031
032import org.apache.directory.api.ldap.model.entry.Attribute;
033import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
034import org.apache.directory.api.ldap.model.entry.DefaultEntry;
035import org.apache.directory.api.ldap.model.entry.Entry;
036import org.apache.directory.api.ldap.model.exception.LdapException;
037import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
038import org.apache.directory.api.ldap.model.name.Dn;
039import org.apache.directory.api.ldap.model.name.Rdn;
040import org.apache.directory.api.ldap.model.schema.AttributeType;
041import org.apache.directory.api.ldap.model.schema.SchemaManager;
042import org.apache.directory.mavibot.btree.serializer.AbstractElementSerializer;
043import org.apache.directory.mavibot.btree.serializer.BufferHandler;
044import org.apache.directory.server.i18n.I18n;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048
049/**
050 * Serialize and deserialize a ServerEntry. There is a big difference with the standard
051 * Entry serialization : we don't serialize the entry's Dn, we just serialize it's Rdn.
052 * <br><br>
053 * <b>This class must *not* be used outside of the server.</b>
054 *  
055 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
056 */
057public class MavibotEntrySerializer extends AbstractElementSerializer<Entry>
058{
059    /** The serialVersionUID */
060    private static final long serialVersionUID = 1L;
061
062    /** the logger for this class */
063    private static final Logger LOG = LoggerFactory.getLogger( MavibotEntrySerializer.class );
064
065    /**
066     * Speedup for logs
067     */
068    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
069
070    /** The schemaManager reference */
071    private static SchemaManager schemaManager;
072
073    private static class EntryComparator implements Comparator<Entry>
074    {
075
076        @Override
077        public int compare( Entry entry1, Entry entry2 )
078        {
079            return entry1.getDn().getName().compareTo( entry1.getDn().getName() );
080        }
081
082    }
083
084    private static Comparator<Entry> comparator = new EntryComparator();
085
086
087    /**
088     * Creates a new instance of ServerEntrySerializer.
089     * The schemaManager MUST be set explicitly using the static {@link #setSchemaManager(SchemaManager)}
090     */
091    public MavibotEntrySerializer()
092    {
093        super( comparator );
094    }
095
096
097    @Override
098    public Comparator<Entry> getComparator()
099    {
100        return comparator;
101    }
102
103
104    /**
105     * <p>
106     * 
107     * This is the place where we serialize entries, and all theirs
108     * elements. the reason why we don't call the underlying methods
109     * (<code>ServerAttribute.write(), Value.write()</code>) is that we need
110     * access to the registries to read back the values.
111     * <p>
112     * The structure used to store the entry is the following :
113     * <ul>
114     *   <li><b>[a byte]</b> : if the Dn is empty 0 will be written else 1</li>
115     *   <li><b>[Rdn]</b> : The entry's Rdn.</li>
116     *   <li><b>[numberAttr]</b> : the bumber of attributes. Can be 0</li>
117     *   <li>For each Attribute :
118     *     <ul>
119     *       <li><b>[attribute's oid]</b> : The attribute's OID to get back
120     *       the attributeType on deserialization</li>
121     *       <li><b>[Attribute]</b> The attribute</li>
122     *     </ul>
123     *   </li>
124     * </ul>
125     */
126    public byte[] serialize( Entry entry )
127    {
128        try
129        {
130            ByteArrayOutputStream baos = new ByteArrayOutputStream();
131
132            ObjectOutput out = new ObjectOutputStream( baos );
133
134            // First, the Dn
135            Dn dn = entry.getDn();
136
137            // Write the Rdn of the Dn
138            if ( dn.isEmpty() )
139            {
140                out.writeByte( 0 );
141            }
142            else
143            {
144                out.writeByte( 1 );
145                Rdn rdn = dn.getRdn();
146                rdn.writeExternal( out );
147            }
148
149            // Then the attributes.
150            out.writeInt( entry.getAttributes().size() );
151
152            // Iterate through the keys. We store the Attribute
153            // here, to be able to restore it in the readExternal :
154            // we need access to the registries, which are not available
155            // in the ServerAttribute class.
156            for ( Attribute attribute : entry.getAttributes() )
157            {
158                AttributeType attributeType = attribute.getAttributeType();
159
160                // Write the oid to be able to restore the AttributeType when deserializing
161                // the attribute
162                String oid = attributeType.getOid();
163
164                out.writeUTF( oid );
165
166                // Write the attribute
167                attribute.writeExternal( out );
168            }
169
170            out.flush();
171
172            // Note : we don't store the ObjectClassAttribute. It has already
173            // been stored as an attribute.
174
175            if ( IS_DEBUG )
176            {
177                LOG.debug( ">------------------------------------------------" );
178                LOG.debug( "Serialize {}", entry );
179            }
180
181            return baos.toByteArray();
182        }
183        catch ( Exception e )
184        {
185            throw new RuntimeException( e );
186        }
187    }
188
189
190    /**
191     *  Deserialize a Entry.
192     *  
193     *  @param buffer The buffer containing the serialized entry
194     *  @return An instance of a Entry object 
195     *  @throws IOException if we can't deserialize the Entry
196     */
197    public Entry deserialize( ByteBuffer buffer ) throws IOException
198    {
199        // read the length
200        int len = buffer.limit();
201
202        ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buffer.array(), buffer.position(), len ) );
203
204        try
205        {
206            Entry entry = new DefaultEntry( schemaManager );
207
208            // Read the Dn, if any
209            byte hasDn = in.readByte();
210
211            if ( hasDn == 1 )
212            {
213                Rdn rdn = new Rdn( schemaManager );
214                rdn.readExternal( in );
215
216                try
217                {
218                    entry.setDn( new Dn( schemaManager, rdn ) );
219                }
220                catch ( LdapInvalidDnException lide )
221                {
222                    IOException ioe = new IOException( lide.getMessage() );
223                    ioe.initCause( lide );
224                    throw ioe;
225                }
226            }
227            else
228            {
229                entry.setDn( Dn.EMPTY_DN );
230            }
231
232            // Read the number of attributes
233            int nbAttributes = in.readInt();
234
235            // Read the attributes
236            for ( int i = 0; i < nbAttributes; i++ )
237            {
238                // Read the attribute's OID
239                String oid = in.readUTF();
240
241                try
242                {
243                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
244
245                    // Create the attribute we will read
246                    Attribute attribute = new DefaultAttribute( attributeType );
247
248                    // Read the attribute
249                    attribute.readExternal( in );
250
251                    entry.add( attribute );
252                }
253                catch ( LdapException ne )
254                {
255                    // We weren't able to find the OID. The attribute will not be added
256                    throw new ClassNotFoundException( ne.getMessage(), ne );
257                }
258            }
259
260            buffer.position( buffer.position() + len ); // previous position + length
261
262            return entry;
263        }
264        catch ( ClassNotFoundException cnfe )
265        {
266            LOG.error( I18n.err( I18n.ERR_134, cnfe.getLocalizedMessage() ) );
267            throw new IOException( cnfe.getLocalizedMessage() );
268        }
269    }
270
271
272    @Override
273    public Entry deserialize( BufferHandler bufferHandler ) throws IOException
274    {
275        return deserialize( ByteBuffer.wrap( bufferHandler.getBuffer() ) );
276    }
277
278
279    public static void setSchemaManager( SchemaManager schemaManager )
280    {
281        MavibotEntrySerializer.schemaManager = schemaManager;
282    }
283
284
285    /**
286     * {@inheritDoc}
287     */
288    @Override
289    public Entry fromBytes( byte[] buffer ) throws IOException
290    {
291        return fromBytes( buffer, 0 );
292    }
293
294
295    /**
296     * {@inheritDoc}
297     */
298    @Override
299    public Entry fromBytes( byte[] buffer, int pos ) throws IOException
300    {
301        // read the length
302        int len = buffer.length - pos;
303
304        ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buffer, pos, len ) );
305
306        try
307        {
308            Entry entry = new DefaultEntry( schemaManager );
309
310            // Read the Dn, if any
311            byte hasDn = in.readByte();
312
313            if ( hasDn == 1 )
314            {
315                Rdn rdn = new Rdn( schemaManager );
316                rdn.readExternal( in );
317
318                try
319                {
320                    entry.setDn( new Dn( schemaManager, rdn ) );
321                }
322                catch ( LdapInvalidDnException lide )
323                {
324                    IOException ioe = new IOException( lide.getMessage() );
325                    ioe.initCause( lide );
326                    throw ioe;
327                }
328            }
329            else
330            {
331                entry.setDn( Dn.EMPTY_DN );
332            }
333
334            // Read the number of attributes
335            int nbAttributes = in.readInt();
336
337            // Read the attributes
338            for ( int i = 0; i < nbAttributes; i++ )
339            {
340                // Read the attribute's OID
341                String oid = in.readUTF();
342
343                try
344                {
345                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
346
347                    // Create the attribute we will read
348                    Attribute attribute = new DefaultAttribute( attributeType );
349
350                    // Read the attribute
351                    attribute.readExternal( in );
352
353                    entry.add( attribute );
354                }
355                catch ( LdapException ne )
356                {
357                    // We weren't able to find the OID. The attribute will not be added
358                    throw new ClassNotFoundException( ne.getMessage(), ne );
359                }
360            }
361
362            return entry;
363        }
364        catch ( ClassNotFoundException cnfe )
365        {
366            LOG.error( I18n.err( I18n.ERR_134, cnfe.getLocalizedMessage() ) );
367            throw new IOException( cnfe.getLocalizedMessage() );
368        }
369    }
370
371
372    /**
373     * {@inheritDoc}
374     */
375    @Override
376    public Class<?> getType()
377    {
378        return Entry.class;
379    }
380}