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 */
020
021package org.apache.directory.api.ldap.sp;
022
023
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.Serializable;
028import java.net.URISyntaxException;
029import java.net.URL;
030
031import javax.naming.NamingException;
032import javax.naming.directory.Attributes;
033import javax.naming.directory.BasicAttributes;
034import javax.naming.ldap.ExtendedRequest;
035import javax.naming.ldap.ExtendedResponse;
036import javax.naming.ldap.LdapContext;
037
038import org.apache.commons.lang.SerializationUtils;
039import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
040import org.apache.directory.api.ldap.extras.extended.storedProcedure.StoredProcedureRequestImpl;
041import org.apache.directory.api.ldap.model.constants.SchemaConstants;
042import org.apache.directory.api.util.IOUtils;
043
044
045/**
046 * A utility class for working with Java Stored Procedures at the base level.
047 *
048 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
049 */
050public final class JavaStoredProcUtils
051{
052
053    /**
054     * Private constructor.
055     */
056    private JavaStoredProcUtils()
057    {
058    }
059
060
061    /**
062     * Returns the stream data of a Java class.
063     * 
064     * @param clazz
065     *           The class whose stream data will be retrieved.
066     * @return
067     *           Stream data of the class file as a byte array.
068     * @throws NamingException
069     *           If an IO error occurs during reading the class file.
070     */
071    public static byte[] getClassFileAsStream( Class<?> clazz ) throws NamingException
072    {
073        String fullClassName = clazz.getName();
074        int lastDot = fullClassName.lastIndexOf( '.' );
075        String classFileName = fullClassName.substring( lastDot + 1 ) + ".class";
076        URL url = clazz.getResource( classFileName );
077        InputStream in;
078        
079        try
080        {
081            in = url.openStream();
082        }
083        catch ( IOException ioe )
084        {
085            NamingException ne = new NamingException();
086            ne.setRootCause( ioe );
087            throw ne;
088        }
089        
090        File file;
091        
092        try
093        {
094            file = new File( url.toURI() );
095        }
096        catch ( URISyntaxException urie )
097        {
098            NamingException ne = new NamingException();
099            ne.setRootCause( urie );
100            throw ne;
101        }
102        
103        int size = ( int ) file.length();
104        byte[] buf = new byte[size];
105
106        try
107        {
108            in.read( buf );
109        }
110        catch ( IOException e )
111        {
112            NamingException ne = new NamingException();
113            ne.setRootCause( e );
114            throw ne;
115        }
116        finally
117        {
118            IOUtils.closeQuietly( in );
119        }
120
121        return buf;
122    }
123
124
125    /**
126     * Loads a Java class's stream data as a subcontext of an LdapContext given.
127     * 
128     * @param ctx
129     *           The parent context of the Java class entry to be loaded.
130     * @param clazz
131     *           Class to be loaded.
132     * @throws NamingException
133     *           If an error occurs during creating the subcontext.
134     */
135    public static void loadStoredProcedureClass( LdapContext ctx, Class<?> clazz ) throws NamingException
136    {
137        byte[] buf = getClassFileAsStream( clazz );
138        String fullClassName = clazz.getName();
139
140        Attributes attributes = new BasicAttributes( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, true );
141        attributes.get( SchemaConstants.OBJECT_CLASS_AT ).add( "storedProcUnit" );
142        attributes.get( SchemaConstants.OBJECT_CLASS_AT ).add( "javaStoredProcUnit" );
143        attributes.put( "storedProcLangId", "Java" );
144        attributes.put( "storedProcUnitName", fullClassName );
145        attributes.put( "javaByteCode", buf );
146
147        ctx.createSubcontext( "storedProcUnitName=" + fullClassName, attributes );
148    }
149
150
151    /**
152     * Invoke a Stored Procedure
153     * 
154     * @param ctx The execution context
155     * @param procedureName The procedure to execute
156     * @param arguments The procedure's arguments
157     * @return The execution resut
158     * @throws NamingException If we have had an error whil executing the stored procedure
159     */
160    public static Object callStoredProcedure( LdapContext ctx, String procedureName, Object[] arguments )
161        throws NamingException
162    {
163        String language = "Java";
164
165        Object responseObject;
166        
167        try
168        {
169            /**
170             * Create a new stored procedure execution request.
171             */
172            StoredProcedureRequestImpl req = new StoredProcedureRequestImpl( 0, procedureName, language );
173
174            /**
175             * For each argument UTF-8-encode the type name
176             * and Java-serialize the value
177             * and add them to the request as a parameter object.
178             */
179            for ( int i = 0; i < arguments.length; i++ )
180            {
181                byte[] type;
182                byte[] value;
183                type = arguments[i].getClass().getName().getBytes( "UTF-8" );
184                value = SerializationUtils.serialize( ( Serializable ) arguments[i] );
185                req.addParameter( type, value );
186            }
187
188            /**
189             * Call the stored procedure via the extended operation
190             * and get back its return value.
191             */
192            ExtendedRequest jndiReq = LdapApiServiceFactory.getSingleton().toJndi( req );
193            ExtendedResponse resp = ctx.extendedOperation( jndiReq );
194
195            /**
196             * Restore a Java object from the return value.
197             */
198            byte[] responseStream = resp.getEncodedValue();
199            responseObject = SerializationUtils.deserialize( responseStream );
200        }
201        catch ( Exception e )
202        {
203            NamingException ne = new NamingException();
204            ne.setRootCause( e );
205            throw ne;
206        }
207
208        return responseObject;
209    }
210}