/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #ifdef LINUX #undef minor #undef major #endif #include #if defined __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-attributes" #endif #include #if defined __clang__ #pragma clang diagnostic pop #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // this one is header-only #include namespace com::sun::star::registry { class XRegistryKey; } using namespace css::java; using namespace css::lang; using namespace css::loader; using namespace css::uno; using namespace css::registry; using namespace ::cppu; using namespace ::osl; namespace stoc_javaloader { namespace { // from desktop/source/deployment/misc/dp_misc.cxx OUString generateRandomPipeId() { // compute some good pipe id: static rtlRandomPool s_hPool = rtl_random_createPool(); if (s_hPool == nullptr) throw RuntimeException( "cannot create random pool!?", nullptr ); sal_uInt8 bytes[ 32 ]; if (rtl_random_getBytes( s_hPool, bytes, SAL_N_ELEMENTS(bytes) ) != rtl_Random_E_None) { throw RuntimeException( "random pool error!?", nullptr ); } OUStringBuffer buf; for (unsigned char byte : bytes) { buf.append( static_cast(byte), 0x10 ); } return buf.makeStringAndClear(); } // from desktop/source/deployment/registry/component/dp_component.cxx /** return a vector of bootstrap variables which have been provided as command arguments. */ std::vector getCmdBootstrapVariables() { std::vector ret; sal_uInt32 count = osl_getCommandArgCount(); for (sal_uInt32 i = 0; i < count; i++) { OUString arg; osl_getCommandArg(i, &arg.pData); if (arg.startsWith("-env:")) ret.push_back(arg); } return ret; } // from desktop/source/deployment/misc/dp_misc.cxx oslProcess raiseProcess( OUString const & appURL, Sequence const & args ) { ::osl::Security sec; oslProcess hProcess = nullptr; oslProcessError rc = osl_executeProcess( appURL.pData, reinterpret_cast( const_cast(args.getConstArray()) ), args.getLength(), osl_Process_DETACHED, sec.getHandle(), nullptr, // => current working dir nullptr, 0, // => no env vars &hProcess ); switch (rc) { case osl_Process_E_None: break; case osl_Process_E_NotFound: throw RuntimeException( "image not found!", nullptr ); case osl_Process_E_TimedOut: throw RuntimeException( "timeout occurred!", nullptr ); case osl_Process_E_NoPermission: throw RuntimeException( "permission denied!", nullptr ); case osl_Process_E_Unknown: throw RuntimeException( "unknown error!", nullptr ); case osl_Process_E_InvalidError: default: throw RuntimeException( "unmapped error!", nullptr ); } return hProcess; } // from desktop/source/deployment/registry/component/dp_component.cxx Reference raise_uno_process( Reference const & xContext) { OSL_ASSERT( xContext.is() ); OUString const url(css::util::theMacroExpander::get(xContext)->expandMacros("$URE_BIN_DIR/uno")); const OUString connectStr = "uno:pipe,name=" + generateRandomPipeId() + ";urp;uno.ComponentContext"; // raise core UNO process to register/run a component, // javavm service uses unorc next to executable to retrieve deployed // jar typelibs std::vector args{ #if OSL_DEBUG_LEVEL == 0 "--quiet", #endif "--singleaccept", "-u", connectStr, // don't inherit from unorc: "-env:INIFILENAME=" }; //now add the bootstrap variables which were supplied on the command line std::vector bootvars = getCmdBootstrapVariables(); args.insert(args.end(), bootvars.begin(), bootvars.end()); oslProcess hProcess; try { hProcess = raiseProcess(url, comphelper::containerToSequence(args)); } catch (...) { OUStringBuffer sMsg = "error starting process: " + url; for (const auto& arg : args) { sMsg.append(" " + arg); } throw css::uno::RuntimeException(sMsg.makeStringAndClear()); } try { // from desktop/source/deployment/misc/dp_misc.cxx Reference const xUnoUrlResolver( css::bridge::UnoUrlResolver::create(xContext) ); for (int i = 0; i <= 40; ++i) // 20 seconds { try { return Reference( xUnoUrlResolver->resolve(connectStr), UNO_QUERY_THROW ); } catch (const css::connection::NoConnectException &) { if (i < 40) { ::osl::Thread::wait( std::chrono::milliseconds(500) ); } else throw; } } return nullptr; // warning C4715 } catch (...) { // try to terminate process: if ( osl_terminateProcess( hProcess ) != osl_Process_E_None ) { OSL_ASSERT( false ); } throw; } } class JavaComponentLoader : protected ::cppu::BaseMutex , public WeakComponentImplHelper { /** local context */ css::uno::Reference m_xComponentContext; /** possible remote process' context (use depends on configuration). note: lifetime must be effectively "static" as this JavaComponentLoader has no control over the lifetime of the services created via this context; hence JavaComponentLoader is a single-instance service. */ css::uno::Reference m_xRemoteComponentContext; /** Do not use m_javaLoader directly. Instead use getJavaLoader. This is either an in-process loader implemented in Java, or a remote instance of JavaComponentLoader running in uno process, acting as a proxy. */ css::uno::Reference m_javaLoader; /** The returned Reference contains a null pointer if the office is not configured to run java. @exception css::uno::RuntimeException If the Java implementation of the loader could not be obtained, for reasons other then that java was not configured the RuntimeException is thrown. */ const css::uno::Reference & getJavaLoader(OUString &); public: /// @throws RuntimeException explicit JavaComponentLoader(const css::uno::Reference & xCtx); public: // XServiceInfo virtual OUString SAL_CALL getImplementationName() override; virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; virtual Sequence SAL_CALL getSupportedServiceNames() override; virtual void SAL_CALL disposing() override; // XImplementationLoader virtual css::uno::Reference SAL_CALL activate( const OUString& implementationName, const OUString& implementationLoaderUrl, const OUString& locationUrl, const css::uno::Reference& xKey) override; virtual sal_Bool SAL_CALL writeRegistryInfo( const css::uno::Reference& xKey, const OUString& implementationLoaderUrl, const OUString& locationUrl) override; }; } void JavaComponentLoader::disposing() { // Explicitly drop all remote refs to shut down the uno.bin process // and particularly the connection to it, so that it can't do more calls // during late shutdown. m_javaLoader.clear(); if (m_xRemoteComponentContext.is()) { Reference const xComp(m_xRemoteComponentContext, UNO_QUERY); assert(xComp.is()); xComp->dispose(); m_xRemoteComponentContext.clear(); } } const css::uno::Reference & JavaComponentLoader::getJavaLoader(OUString & rRemoteArg) { static Mutex ourMutex; MutexGuard aGuard(ourMutex); if (m_javaLoader.is()) return m_javaLoader; // check if the JVM should be instantiated out-of-process if (rRemoteArg.isEmpty()) { if (!m_xRemoteComponentContext.is()) { Reference const xConf( m_xComponentContext->getServiceManager()->createInstanceWithArgumentsAndContext( "com.sun.star.configuration.ReadOnlyAccess", { Any(OUString("*")) }, // locale isn't relevant here m_xComponentContext), UNO_QUERY); // configmgr is not part of URE, so may not exist! if (xConf.is()) { Any const value(xConf->getByHierarchicalName( "org.openoffice.Office.Java/VirtualMachine/RunUnoComponentsOutOfProcess")); bool b; if ((value >>= b) && b) { SAL_INFO("stoc.java", "JavaComponentLoader: starting uno process"); m_xRemoteComponentContext = raise_uno_process(m_xComponentContext); } } } if (m_xRemoteComponentContext.is()) { SAL_INFO("stoc.java", "JavaComponentLoader: creating remote instance to start JVM in uno process"); // create JVM service in remote uno.bin process Reference const xLoader( m_xRemoteComponentContext->getServiceManager()->createInstanceWithContext( "com.sun.star.loader.Java2", m_xRemoteComponentContext), UNO_QUERY_THROW); assert(xLoader.is()); m_javaLoader = xLoader; rRemoteArg = "remote"; SAL_INFO("stoc.java", "JavaComponentLoader: remote proxy instance created: " << m_javaLoader.get()); return m_javaLoader; } } uno_Environment * pJava_environment = nullptr; uno_Environment * pUno_environment = nullptr; typelib_InterfaceTypeDescription * pType_XImplementationLoader = nullptr; try { // get a java vm, where we can create a loader css::uno::Reference javaVM_xJavaVM( m_xComponentContext->getValueByName( ("/singletons/" "com.sun.star.java.theJavaVirtualMachine")), UNO_QUERY_THROW); // Use the special protocol of XJavaVM.getJavaVM: If the passed in // process ID has an extra 17th byte of value one, the returned any // contains a pointer to a jvmaccess::UnoVirtualMachine, instead of the // underlying JavaVM pointer: Sequence processID(17); rtl_getGlobalProcessId(reinterpret_cast(processID.getArray())); processID.getArray()[16] = 1; // We get a non-refcounted pointer to a jvmaccess::UnoVirtualMachine // from the XJavaVM service (the pointer is guaranteed to be valid // as long as our reference to the XJavaVM service lasts), and // convert the non-refcounted pointer into a refcounted one // immediately: static_assert(sizeof (sal_Int64) >= sizeof (jvmaccess::UnoVirtualMachine *), "must be at least the same size"); sal_Int64 nPointer = reinterpret_cast< sal_Int64 >( static_cast< jvmaccess::UnoVirtualMachine * >(nullptr)); javaVM_xJavaVM->getJavaVM(processID) >>= nPointer; rtl::Reference< jvmaccess::UnoVirtualMachine > xVirtualMachine( reinterpret_cast< jvmaccess::UnoVirtualMachine * >(nPointer)); if (!xVirtualMachine.is()) { //throw RuntimeException( // "javaloader error - JavaVirtualMachine service could not provide a VM", // css::uno::Reference()); // We must not throw a RuntimeException, because this might end the applications. // It is ok if java components // are not working because the office can be installed without Java support. SAL_WARN("stoc", "getJavaVM returned null"); return m_javaLoader; // null-ref } try { jvmaccess::VirtualMachine::AttachGuard aGuard2( xVirtualMachine->getVirtualMachine()); JNIEnv * pJNIEnv = aGuard2.getEnvironment(); // instantiate the java JavaLoader jclass jcClassLoader = pJNIEnv->FindClass("java/lang/ClassLoader"); if(pJNIEnv->ExceptionOccurred()) throw RuntimeException( "javaloader error - could not find class java/lang/ClassLoader"); jmethodID jmLoadClass = pJNIEnv->GetMethodID( jcClassLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); if(pJNIEnv->ExceptionOccurred()) throw RuntimeException( "javaloader error - could not find method java/lang/ClassLoader.loadClass"); jvalue arg; arg.l = pJNIEnv->NewStringUTF( "com.sun.star.comp.loader.JavaLoader"); if(pJNIEnv->ExceptionOccurred()) throw RuntimeException( "javaloader error - could not create string"); jclass jcJavaLoader = static_cast< jclass >( pJNIEnv->CallObjectMethodA( static_cast< jobject >(xVirtualMachine->getClassLoader()), jmLoadClass, &arg)); if(pJNIEnv->ExceptionOccurred()) throw RuntimeException( "javaloader error - could not find class com/sun/star/comp/loader/JavaLoader"); jmethodID jmJavaLoader_init = pJNIEnv->GetMethodID(jcJavaLoader, "", "()V"); if(pJNIEnv->ExceptionOccurred()) throw RuntimeException( "javaloader error - instantiation of com.sun.star.comp.loader.JavaLoader failed"); jobject joJavaLoader = pJNIEnv->NewObject(jcJavaLoader, jmJavaLoader_init); if(pJNIEnv->ExceptionOccurred()) throw RuntimeException( "javaloader error - instantiation of com.sun.star.comp.loader.JavaLoader failed"); // map the java JavaLoader to this environment OUString sJava("java"); uno_getEnvironment(&pJava_environment, sJava.pData, xVirtualMachine.get()); if(!pJava_environment) throw RuntimeException( "javaloader error - no Java environment available"); // why is there no convenient constructor? OUString sCppu_current_lb_name(CPPU_CURRENT_LANGUAGE_BINDING_NAME); uno_getEnvironment(&pUno_environment, sCppu_current_lb_name.pData, nullptr); if(!pUno_environment) throw RuntimeException( "javaloader error - no C++ environment available"); Mapping java_curr(pJava_environment, pUno_environment); if(!java_curr.is()) throw RuntimeException( "javaloader error - no mapping from java to C++ "); // release java environment pJava_environment->release(pJava_environment); pJava_environment = nullptr; // release uno environment pUno_environment->release(pUno_environment); pUno_environment = nullptr; cppu::UnoType::get(). getDescription(reinterpret_cast(&pType_XImplementationLoader)); if(!pType_XImplementationLoader) throw RuntimeException( "javaloader error - no type information for XImplementationLoader"); m_javaLoader.set(static_cast(java_curr.mapInterface(joJavaLoader, pType_XImplementationLoader))); pJNIEnv->DeleteLocalRef( joJavaLoader ); if(!m_javaLoader.is()) throw RuntimeException( "javaloader error - mapping of java XImplementationLoader to c++ failed"); typelib_typedescription_release(reinterpret_cast(pType_XImplementationLoader)); pType_XImplementationLoader = nullptr; } catch (jvmaccess::VirtualMachine::AttachGuard::CreationException &) { css::uno::Any anyEx = cppu::getCaughtException(); throw css::lang::WrappedTargetRuntimeException( "jvmaccess::VirtualMachine::AttachGuard::CreationException", static_cast< cppu::OWeakObject * >(this), anyEx ); } // set the service manager at the javaloader css::uno::Reference javaLoader_XInitialization(m_javaLoader, UNO_QUERY_THROW); Any any; any <<= m_xComponentContext->getServiceManager(); javaLoader_XInitialization->initialize(Sequence(&any, 1)); } catch(RuntimeException &) { if(pJava_environment) pJava_environment->release(pJava_environment); if(pUno_environment) pUno_environment->release(pUno_environment); if(pType_XImplementationLoader) typelib_typedescription_release( reinterpret_cast(pType_XImplementationLoader)); throw; } SAL_INFO("stoc", "javaloader.cxx: mapped javaloader - 0x" << m_javaLoader.get()); return m_javaLoader; } JavaComponentLoader::JavaComponentLoader(const css::uno::Reference & xCtx) : WeakComponentImplHelper(m_aMutex) , m_xComponentContext(xCtx) { } // XServiceInfo OUString SAL_CALL JavaComponentLoader::getImplementationName() { return "com.sun.star.comp.stoc.JavaComponentLoader"; } sal_Bool SAL_CALL JavaComponentLoader::supportsService(const OUString & ServiceName) { return cppu::supportsService(this, ServiceName); } Sequence SAL_CALL JavaComponentLoader::getSupportedServiceNames() { return { "com.sun.star.loader.Java", "com.sun.star.loader.Java2" }; } // XImplementationLoader sal_Bool SAL_CALL JavaComponentLoader::writeRegistryInfo( const css::uno::Reference & xKey, const OUString & blabla, const OUString & rLibName) { OUString remoteArg(blabla); const css::uno::Reference & loader = getJavaLoader(remoteArg); if (!loader.is()) throw CannotRegisterImplementationException("Could not create Java implementation loader"); return loader->writeRegistryInfo(xKey, remoteArg, rLibName); } css::uno::Reference SAL_CALL JavaComponentLoader::activate( const OUString & rImplName, const OUString & blabla, const OUString & rLibName, const css::uno::Reference & xKey) { OUString remoteArg(blabla); if (rImplName.isEmpty() && blabla.isEmpty() && rLibName.isEmpty()) { // preload JVM was requested (void)getJavaLoader(remoteArg); return css::uno::Reference(); } const css::uno::Reference & loader = getJavaLoader(remoteArg); if (!loader.is()) throw CannotActivateFactoryException("Could not create Java implementation loader"); return loader->activate(rImplName, remoteArg, rLibName, xKey); } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* stoc_JavaComponentLoader_get_implementation( css::uno::XComponentContext* context, css::uno::Sequence const&) { try { return cppu::acquire(new JavaComponentLoader(context)); } catch(const RuntimeException & runtimeException) { SAL_INFO( "stoc", "could not init javaloader due to " << runtimeException); throw; } } } //end namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */