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