Работа с файл сервером по SMB из Oracle

В данном примере предполагается, что у вас Oracle установлен на ОС Linux или Unix, и необходимо скопировать файл в папку «разшаренную» на сервере Windows.
Конечно можно реализовать такое на стороне клиентского приложения или на каком нибудь сервере приложений или интеграционной шине. Но моя реализация ограничивалась только в рамках Oracle с применением его заданий по расписанию (Job).
Как решение я использовал возможность выполнения Java классов в Oracle.

Написание Java класса
В данном примере я использовал пакет jCIFS для работы по протоколу SMB с файл сервером. Свежую версию пакета jCIFS можно будет скачать с сайта разработчика https://jcifs.samba.org/.
За основу я взял пример от сюда.
Для написания класса я использовал IDE Eclipse. Можно конечно и использовать какой нибудь PL/SQL Developer, но для меня на тот момент было удобнее использовать Eclipse для синтаксической проверки и проверки зависимостей между пакетами.
Итак создаем проект и во вкладке Libraries добавляем наш jar пакет jCIFS через кнопку Add External JARs…
Создаем в нашем проекте класс «SambaTest».
В заголовке класса добавляем импорт:
import java.io.*;
import jcifs.smb.*;
Добавляем метод copyFileToSamba с возвратом текста ошибки в случае не удачного выполнения по следующему примеру:
public static String copyFileToSamba(String inNameSource, String inNameDest, String inUsr, String inPass){
String res = "OK";
try{
// Создаем объект аутентификатор
NtlmPasswordAuthentication auth =
new NtlmPasswordAuthentication("DOMAIN", inUsr, inPass);
// Читаем содержимое исходного файла
File vFileSource = new File(inNameSource);
InputStream vISFile = new FileInputStream(vFileSource);
SmbFile vSmbFile = new SmbFile(inNameDest, auth);
// Создаем объект для потока куда мы будем писать наша файл
SmbFileOutputStream vDestFile = new SmbFileOutputStream(vSmbFile);
// Ну и копируем все из исходного потока в поток назначения.
BufferedReader vBrl = new BufferedReader(new InputStreamReader(vISFile));
String b = null;
while((b=vBrl.readLine())!=null){
vDestFile.write(b.getBytes());
}
vDestFile.flush();
vBrl.close();
vDestFile.close();
}
catch(Exception e){
res = "Ошибка! Файл источник-"+inNameSource+" Файл приемник-"+inNameDest+" Текст-"+e;
}
return res;
}
Во входящих параметрах inNameSource это путь к файлу источнику на локальном сервере где установлен Oracle (пример: /home/user/test.txt). В параметре inNameDest передается файл получатель на файл сервере Windows (пример: smb://fileserver/papka/test.txt).
Учетные данные пользователя сделал как входящие в параметрах inUsr и inPass, так как не секьюрно. Их необходимо криптовать если хранить в базе данных.
Установка в Oracle
Устанавливаем jCIFS в Oracle следующей командой:
loadjava -u USER/PASSWORD@SERVER -oci8 -resolve jcifs-1.3.19.jar
При установке может ломаться следующим текстом:
errors : class jcifs/http/NetworkExplorer
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletException
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServlet
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletResponse
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletOutputStream
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletRequest
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpSession
errors : class jcifs/http/NtlmHttpFilter
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletRequest
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletResponse
ORA-29521: не удалось найти ссылочное имя javax/servlet/Filter
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletException
ORA-29521: не удалось найти ссылочное имя javax/servlet/FilterConfig
ORA-29521: не удалось найти ссылочное имя javax/servlet/FilterChain
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpSession
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletRequest
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletResponse
errors : class jcifs/http/NtlmHttpServletRequest
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletRequestWrapper
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletRequest
errors : class jcifs/http/NtlmServlet
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServlet
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletException
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletConfig
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletRequest
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletResponse
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpSession
errors : class jcifs/http/NtlmSsp
ORA-29521: не удалось найти ссылочное имя javax/servlet/ServletException
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletRequest
ORA-29521: не удалось найти ссылочное имя javax/servlet/http/HttpServletResponse
The following operations failed
class jcifs/http/NetworkExplorer: resolution
class jcifs/http/NtlmHttpFilter: resolution
class jcifs/http/NtlmHttpServletRequest: resolution
class jcifs/http/NtlmServlet: resolution
class jcifs/http/NtlmSsp: resolution
exiting : Failures occurred during processing
Решается это дополнительной установкой пакета javax.servlet-3.0.jar. Скачать его можно от сюда
Устанавливаем пакет javax.servlet-3.0.jar
loadjava -u USER/PASSWORD@SERVER -oci8 -resolve javax.servlet-3.0.jar
Затем повторяем попытку установки пакета jCIFS как описано выше.
После того как установили все необходимые пакеты можно установить созданный нами Java класс «SambaTest»
loadjava -u USER/PASSWORD@SERVER -oci8 -resolve SambaTest.java
При установке таким способом ваш Java Source будет доступен для просмотра и редактирования в Oracle.
Можно установить и скомпилироавнный пакет вашего класса
loadjava -u USER/PASSWORD@SERVER -oci8 -resolve SambaTest.class
Но имейте ввиду, что могут быть не соответствия версии Java на локале где вы компилируите класс и Oracl-ом. При несоответствии версии Java во время установки вернется ошибка ORA-29537.
Проверить версию Java в Oracle можно скриптом:
SELECT dbms_java.get_ojvm_property(PROPSTRING=>'java.version') as "java.version"
FROM dual;
java.version
----------------------
1.8.0_72
Если вы в процессе установки получите в ответ от Oracle ошибку ORA-29521, то необходимо установить пакет описанный в ошибке.
Написание PL/SQL функции
Итак, создаем в Oracle функцию «CopySmb» по следующему примеру:
function CopySmb(inNameSource in varchar2, inNameDest in varchar2, inUsr in varchar2, inPassword in varchar2) return varchar2
as language java name 'SambaTest.copyFileToSamba(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
return java.lang.String';
Ошибки и их обработка
Если при выполнении операции происходит следующая ошибка:
jcifs.smb.SmbException: Failed to connect: fileserver/XXX.XXX.XXX.XXX
jcifs.util.transport.TransportException
java.security.AccessControlException: the Permission (java.net.SocketPermission XXX.XXX.XXX.XXX:445 connect,resolve) has not been granted to USER. The PL/SQL to grant this is dbms_java.grant_permission( 'USER', 'SYS:java.net.SocketPermission', 'XXX.XXX.XXX.XXX:445', 'connect,resolve' )
at java.security.AccessControlContext.checkPermission(AccessControlContext.java)
at java.security.AccessController.checkPermission(AccessController.java)
at java.lang.SecurityManager.checkPermission(SecurityManager.java)
at oracle.aurora.rdbms.SecurityManagerImpl.checkPermission(SecurityManagerImpl.java)
at java.lang.SecurityManager.checkConnect(SecurityManager.java)
at java.net.Socket.connect(Socket.java)
at jcifs.smb.SmbTransport.negotiate(SmbTransport.java:264)
at jcifs.smb.SmbTransport.doConnect(SmbTransport.java:319)
at jcifs.util.transport.Transport.run(Transport.java:241)
at jcifs.util.transport.Transport.run(Transport.java:258)
В ошибке есть следующая подсказка
java.security.AccessControlException: the Permission (java.net.SocketPermission XXX.XXX.XXX.XXX:445 connect,resolve) has not been granted to USER. The PL/SQL to grant this is dbms_java.grant_permission( 'USER', 'SYS:java.net.SocketPermission', 'XXX.XXX.XXX.XXX:445', 'connect,resolve' )[/cc]
Ошибка говорит о том, что в таблице dba_java_policy отсутствует разрешение на соединение к серверу XXX.XXX.XXX.XXX по порту 445. Доступ может дать пользователь с правами DBA по команде: [cc lang="plsql"]dbms_java.grant_permission( 'USER', 'SYS:java.net.SocketPermission', 'XXX.XXX.XXX.XXX:445', 'connect,resolve' );
В пакете jCIFS также реализован класс SmbException для обработки ошибок сервера. Но есть одна проблема при обработки ошибок через класс SmbException. Если возникнет ошибка java.security.AccessControlException этот класс не покажет их, а просто скажет Failed to connect: fileserver/XXX.XXX.XXX.XXX, и тут могут возникнуть гадания относительно проблем на стороне файервола.
Ниже пример реализации метода обработки ошибок через класс SmbException:
public static String GetSmbErrMsg(SmbException inException, String inFileName){
String res = "";
String vFileName = inFileName.replace("/", "\\\").replace("smb:", "");
if (SmbException.NT_STATUS_OBJECT_PATH_NOT_FOUND == inException.getNtStatus()){
res = "Ошибка! Путь к файлу "+vFileName+" не существует";
}
else if(SmbException.NT_STATUS_LOGON_FAILURE == inException.getNtStatus()) {
res = "Ошибка! Ошибка авторизации";
}
else if(SmbException.NT_STATUS_ACCESS_DENIED == inException.getNtStatus()) {
res = "Ошибка! Нет доступа по пути "+vFileName;
}
else if(SmbException.NT_STATUS_OBJECT_NAME_COLLISION == inException.getNtStatus()) {
res = "Ошибка! Файл "+vFileName+" уже существует";
}
else if(SmbException.NT_STATUS_ACCOUNT_DISABLED == inException.getNtStatus()) {
res = "Ошибка! Учетная запись не активна";
}
else if(SmbException.NT_STATUS_ACCOUNT_LOCKED_OUT == inException.getNtStatus()) {
res = "Ошибка! Учетная запись заблокирована";
}
else
{
res = "Ошибка! "+inException.getLocalizedMessage()+" "+inException.getNtStatus();
}
return res;
}
В принципе вот и все. Остальные возможности jCIFS можно будет прочитать в официальной документации разработчика.
Не судите строго по написанию Java кода, я в Java новичок 🙂
Всем Спасибо!