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.jdbm;
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;
029
030import jdbm.helper.Serializer;
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.server.i18n.I18n;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046
047/**
048 * Serialize and deserialize a ServerEntry. There is a big difference with the standard
049 * Entry serialization : we don't serialize the entry's Dn, we just serialize it's Rdn.
050 * <br><br>
051 * <b>This class must *not* be used outside of the server.</b>
052 *  
053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054 */
055public class EntrySerializer implements Serializer
056{
057    /** The serialVersionUID */
058    private static final long serialVersionUID = 1L;
059
060    /** the logger for this class */
061    private static final Logger LOG = LoggerFactory.getLogger( EntrySerializer.class );
062
063    /**
064     * Speedup for logs
065     */
066    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
067
068    /** The schemaManager reference */
069    private transient SchemaManager schemaManager;
070
071
072    /**
073     * Creates a new instance of ServerEntrySerializer.
074     *
075     * @param schemaManager The reference to the global schemaManager
076     */
077    public EntrySerializer( SchemaManager schemaManager )
078    {
079        this.schemaManager = schemaManager;
080    }
081
082
083    /**
084     * <p>
085     * 
086     * This is the place where we serialize entries, and all theirs
087     * elements. the reason why we don't call the underlying methods
088     * (<code>ServerAttribute.write(), Value.write()</code>) is that we need
089     * access to the registries to read back the values.
090     * <p>
091     * The structure used to store the entry is the following :
092     * <ul>
093     *   <li><b>[a byte]</b> : if the Dn is empty 0 will be written else 1</li>
094     *   <li><b>[Rdn]</b> : The entry's Rdn.</li>
095     *   <li><b>[numberAttr]</b> : the bumber of attributes. Can be 0</li>
096     *   <li>For each Attribute :
097     *     <ul>
098     *       <li><b>[attribute's oid]</b> : The attribute's OID to get back
099     *       the attributeType on deserialization</li>
100     *       <li><b>[Attribute]</b> The attribute</li>
101     *     </ul>
102     *   </li>
103     * </ul>
104     */
105    public byte[] serialize( Object object ) throws IOException
106    {
107        Entry entry = ( Entry ) object;
108
109        ByteArrayOutputStream baos = new ByteArrayOutputStream();
110        ObjectOutput out = new ObjectOutputStream( baos );
111
112        // First, the Dn
113        Dn dn = entry.getDn();
114
115        // Write the Rdn of the Dn
116        if ( dn.isEmpty() )
117        {
118            out.writeByte( 0 );
119        }
120        else
121        {
122            out.writeByte( 1 );
123            Rdn rdn = dn.getRdn();
124            rdn.writeExternal( out );
125        }
126
127        // Then the attributes.
128        out.writeInt( entry.getAttributes().size() );
129
130        // Iterate through the keys. We store the Attribute
131        // here, to be able to restore it in the readExternal :
132        // we need access to the registries, which are not available
133        // in the ServerAttribute class.
134        for ( Attribute attribute : entry.getAttributes() )
135        {
136            AttributeType attributeType = attribute.getAttributeType();
137
138            // Write the oid to be able to restore the AttributeType when deserializing
139            // the attribute
140            String oid = attributeType.getOid();
141
142            out.writeUTF( oid );
143
144            // Write the attribute
145            attribute.writeExternal( out );
146        }
147
148        out.flush();
149
150        // Note : we don't store the ObjectClassAttribute. It has already
151        // been stored as an attribute.
152
153        if ( IS_DEBUG )
154        {
155            LOG.debug( ">------------------------------------------------" );
156            LOG.debug( "Serialize {}", entry );
157        }
158
159        return baos.toByteArray();
160    }
161
162
163    /**
164     *  Deserialize a Entry.
165     *  
166     *  @param bytes the byte array containing the serialized entry
167     *  @return An instance of a Entry object 
168     *  @throws IOException if we can't deserialize the Entry
169     */
170    public Object deserialize( byte[] bytes ) throws IOException
171    {
172        ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( bytes ) );
173
174        try
175        {
176            Entry entry = new DefaultEntry( schemaManager );
177
178            // Read the Dn, if any
179            byte hasDn = in.readByte();
180
181            if ( hasDn == 1 )
182            {
183                Rdn rdn = new Rdn( schemaManager );
184                rdn.readExternal( in );
185
186                try
187                {
188                    entry.setDn( new Dn( schemaManager, rdn ) );
189                }
190                catch ( LdapInvalidDnException lide )
191                {
192                    IOException ioe = new IOException( lide.getMessage() );
193                    ioe.initCause( lide );
194                    throw ioe;
195                }
196            }
197            else
198            {
199                entry.setDn( Dn.EMPTY_DN );
200            }
201
202            // Read the number of attributes
203            int nbAttributes = in.readInt();
204
205            // Read the attributes
206            for ( int i = 0; i < nbAttributes; i++ )
207            {
208                // Read the attribute's OID
209                String oid = in.readUTF();
210
211                try
212                {
213                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
214
215                    // Create the attribute we will read
216                    Attribute attribute = new DefaultAttribute( attributeType );
217
218                    // Read the attribute
219                    attribute.readExternal( in );
220
221                    entry.add( attribute );
222                }
223                catch ( LdapException ne )
224                {
225                    // We weren't able to find the OID. The attribute will not be added
226                    throw new ClassNotFoundException( ne.getMessage(), ne );
227                }
228            }
229
230            return entry;
231        }
232        catch ( ClassNotFoundException cnfe )
233        {
234            LOG.error( I18n.err( I18n.ERR_134, cnfe.getLocalizedMessage() ) );
235            throw new IOException( cnfe.getLocalizedMessage() );
236        }
237    }
238}