    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.hayabusa.report2;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;                 // 6.4.2.0 (2016/01/29)
    019import java.io.File;
    020import java.io.IOException;
    021
    022import org.opengion.fukurou.util.FileUtil;
    023import org.opengion.fukurou.util.StringUtil;
    024import org.opengion.fukurou.system.HybsConst;                                   // 7.2.3.1 (2020/04/17)
    025import org.opengion.fukurou.system.ThrowUtil;                                   // 6.4.2.0 (2016/01/29)
    026import org.opengion.hayabusa.common.HybsSystem;
    027import org.opengion.hayabusa.common.HybsSystemException;
    028import static org.opengion.fukurou.system.HybsConst.FS ;                // 8.0.3.0 (2021/12/17)
    029
    030import com.sun.star.bridge.UnoUrlResolver;
    031import com.sun.star.bridge.XUnoUrlResolver;
    032import com.sun.star.comp.helper.Bootstrap;
    033import com.sun.star.comp.helper.BootstrapException;
    034import com.sun.star.frame.XDesktop;
    035import com.sun.star.frame.XDispatchHelper;
    036import com.sun.star.lang.XMultiComponentFactory;
    037import com.sun.star.uno.UnoRuntime;
    038import com.sun.star.uno.XComponentContext;
    039import com.sun.star.connection.ConnectionSetupException;                // 6.3.9.0 (2015/11/06)
    040
    041/**
    042 * OpenOfficeのﾌﾟﾛｾｽを表すｸﾗｽです｡
    043 *
    044 * bootstrap()ﾒｿｯﾄﾞが呼ばれたﾀｲﾐﾝｸﾞでsoffice.binのﾌﾟﾛｾｽを生成します｡
    045 * soffice.binのﾌﾟﾛｾｽを引数なしで実装した場合､通常は各ﾕｰｻﾞｰで1ﾌﾟﾛｾｽしか
    046 * 生成されないため､-env:UserInstallationの引数を指定することで､仮想的に別ﾕｰｻﾞｰ
    047 * として起動しています｡
    048 * この"ﾕｰｻﾞｰ"を表すｷｰは､ｺﾝｽﾄﾗｸﾀの引数のidです｡
    049 *
    050 * また､この仮想ﾕｰｻﾞｰで起動した場合､初回起動時にﾕｰｻﾞｰ登録を促す画面が立ち上がります｡
    051 * これを回避するため､ﾃﾞﾌｫﾙﾄの環境ﾌｧｲﾙをﾌﾟﾛｾｽ生成前にｺﾋﾟｰすることで､認証済みの
    052 * 状態で立ち上がるようにしています｡
    053 *
    054 * 起動したﾌﾟﾛｾｽとの通知は名前付きﾊﾟｲﾌﾟで行われます｡ﾊﾟｲﾌﾟ名は､"env"+ｺﾝｽﾄﾗｸﾀのidです｡
    055 * ﾌﾟﾛｾｽ起動と､名前付きﾊﾟｲﾌﾟでの接続は非同期で行われます｡
    056 * ﾌﾟﾛｾｽ起動後､60秒経過しても接続できない場合は､BootstrapExceptionが発生します｡
    057 *
    058 * @version  4.0
    059 * @author   Hiroki Nakamura
    060 * @since    JDK5.0,
    061 */
    062public class SOfficeProcess {
    063        /** OOoのｲﾝｽﾄｰﾙﾃﾞｨﾚｸﾄﾘ */
    064        public static final String OFFICE_HOME =
    065//              new File( System.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;
    066//              new File( HybsConst.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;       // 7.2.3.1 (2020/04/17)
    067                new File( HybsConst.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + FS;                           // 8.0.3.0 (2021/12/17)
    068
    069        /** 環境設定のﾊﾟｽ */
    070        // 5.1.7.0 (2010/06/01) 複数ｻｰﾊﾞｰ対応漏れ
    071        public static final String ENV_DIR =
    072                HybsSystem.url2dir( StringUtil.nval( HybsSystem.sys( "REPORT_FILE_URL" )
    073                                                        , HybsSystem.sys( "FILE_URL" ) + "REPORT" + FS ) + "oooenv" ) + FS;
    074        /** 設定ﾌｧｲﾙの雛形 */
    075        private static final String DEFAULT_ENV_PATH =
    076//              OFFICE_HOME + "env" + File.separator + "_default";
    077                OFFICE_HOME + "env" + FS + "_default";                                  // 8.0.3.0 (2021/12/17)
    078
    079        /** soffice.binのﾊﾟｽ */
    080        private static final String SOFFICE_BIN =
    081//              OFFICE_HOME + File.separator + "program" + File.separator + "soffice.bin";
    082                OFFICE_HOME + FS + "program" + FS + "soffice.bin";              // 8.0.3.0 (2021/12/17)
    083
    084        /** ﾛｰｶﾙｺﾝﾃｷｽﾄ */
    085        private static XComponentContext xLocalContext ;
    086
    087        private final String envPath;
    088        private final String envId;                             // 環境設定ﾌｧｲﾙのID
    089
    090        /** ﾘﾓｰﾄﾃﾞｽｸﾄｯﾌﾟｲﾝｽﾀﾝｽ */
    091//      @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なｷｬｽﾄ警告の抑止｡ｷｬｽﾄをはずすと､旧3.1 では､ｴﾗｰになる｡
    092        private XDesktop desktop        ;
    093
    094        private XComponentContext remoteContext ;
    095
    096        /** soffice.binのﾌﾟﾛｾｽ */
    097        private Process process         ;
    098
    099        static {
    100                try {
    101                        xLocalContext = Bootstrap.createInitialComponentContext( null );
    102                }
    103                catch( final Throwable th ) {
    104                        System.out.println( "[ERROR]PROCESS:Can't start LocalContext,Check OFFICE_HOME!" );
    105                        System.err.println( ThrowUtil.ogStackTrace( th ) );                             // 6.4.2.0 (2016/01/29)
    106                }
    107        }
    108
    109        /**
    110         * ｺﾝｽﾄﾗｸﾀです｡
    111         *
    112         * @og.rev 4.3.0.0 (2008/07/15) 設定ﾌｧｲﾙを各ｺﾝﾃｷｽﾄごとに置くように変更
    113         * @param       id      ﾌﾟﾛｾｽID
    114         */
    115        protected SOfficeProcess( final String id ) {
    116                envId = id;
    117                // envPath = OFFICE_HOME + "env" + File.separator + envId;
    118                envPath = ENV_DIR + envId;
    119        }
    120
    121        /**
    122         * OOoへの接続を行います｡
    123         *
    124         * @og.rev 5.0.0.0 (2009/08/03) Linux対応(ﾊﾟｲﾌﾟ名に":"が含まれていると接続できない)
    125         * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
    126         */
    127//      @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なｷｬｽﾄ警告の抑止｡ｷｬｽﾄをはずすと､旧3.1 では､ｴﾗｰになる｡
    128        protected void bootstrap() {
    129                System.out.println( "[INFO]OOo:Starting soffice process,ENV-ID=" + envId );
    130
    131                // check enviroment files, if no files, create from default files
    132                checkEnv( envPath );
    133
    134                // pipe name
    135                // 4.3.3.6 (2008/11/15) ﾏﾙﾁｻｰﾊﾞ対応｡同一ｻｰﾊﾞでの複数実行時不具合のため｡
    136                // 5.0.0.0 (2009/08/03) Linux対応
    137                //String sPipeName = "uno" + envId;
    138                final String sPipeName = "uno" + "_" + HybsSystem.sys("HOST_URL").replace(':','_').replace('/','_') + "_" + envId;
    139
    140                // start office process
    141                // 5.5.2.4 (2012/05/16) int priority は使われていないので､削除します｡
    142                process = execOffice( envPath, sPipeName );
    143                System.out.println( "[INFO]OOo:Invoke soffice.bin,ENV-ID=" + envId );
    144
    145                // create a URL resolver
    146                final XUnoUrlResolver xUrlResolver = UnoUrlResolver.create( xLocalContext );
    147
    148                // connection string
    149                // 5.1.7.0 (2010/06/01) TCP接続対応
    150                final String sConnect = getConnParam( sPipeName );
    151
    152                // wait until office is started
    153                try {
    154                        for( int i=0;; ++i ) {
    155                                try {
    156                                        final Object context = xUrlResolver.resolve( sConnect );
    157//                                      remoteContext = (XComponentContext) UnoRuntime.queryInterface( XComponentContext.class, context );
    158                                        remoteContext = UnoRuntime.queryInterface( XComponentContext.class, context );
    159                                        if( remoteContext == null ) { throw new BootstrapException( "no component context!" ); }
    160                                        break;
    161                                }
    162                                catch( final com.sun.star.connection.NoConnectException ex ) {
    163                                        System.out.println( "[INFO]OOo:Waiting for Connect soffice process,ENV-ID=" + envId );
    164                                        if( i == 60 ) { throw new BootstrapException( ex ); }
    165                                        Thread.sleep( 1000 );
    166                                }
    167                        }
    168
    169                        // create desktop instance
    170                        final XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
    171//                      desktop = (XDesktop) UnoRuntime.queryInterface( XDesktop.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.Desktop", remoteContext ) );
    172                        desktop = UnoRuntime.queryInterface( XDesktop.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.Desktop", remoteContext ) );
    173                }
    174                catch( final ConnectionSetupException
    175                                | BootstrapException
    176                                | IllegalArgumentException
    177                                | InterruptedException ex ) {
    178                        throw new HybsSystemException( "[ERROR]PROCESS:Can't create Desktop Instance", ex );
    179                }
    180//              catch( final Exception ex ) {
    181                catch( final Throwable th ) {           // PMD : 6.9.9.4 (2018/10/01)
    182                        throw new HybsSystemException( "[ERROR]PROCESS:Can't create XDesktop Instance", th );
    183                }
    184
    185                System.out.println( "[INFO]OOo:Connected successful,ENV-ID=" + envId );
    186        }
    187
    188        /**
    189         * Pipe名をｷｰにOpenOfficeのﾌﾟﾛｾｽに接続するための文字列を生成します｡
    190         *
    191         * @param key Pipe名
    192         *
    193         * @return 接続文字列
    194         * @og.rtnNotNull
    195         */
    196        protected String getConnParam( final String key ) {
    197                return "uno:pipe,name=" + key + ";urp;StarOffice.ComponentContext";
    198        }
    199
    200        /**
    201         * ﾃﾞｽｸﾄｯﾌﾟｲﾝｽﾀﾝｽを返します｡
    202         *
    203         * @return ﾃﾞｽｸﾄｯﾌﾟｲﾝｽﾀﾝｽ
    204         */
    205        public XDesktop getDesktop() {
    206                return desktop;
    207        }
    208
    209        /**
    210         * ﾌﾟﾛｾｽを終了します｡
    211         * また､同時に環境設定用のﾌｧｲﾙも削除します｡
    212         *
    213         * @og.rev 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
    214         *
    215         */
    216        public void close() {
    217                // 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
    218                if( process == null ) {
    219                        final String errMsg = "#bootstrap()を先に実行しておいてください｡" ;
    220                        throw new OgRuntimeException( errMsg );
    221                }
    222
    223                process.destroy();
    224                FileUtil.deleteFiles( new File( envPath ) );
    225                System.out.println( "[INFO]OOo:Destroy process,ENV-ID=" + envId );
    226        }
    227
    228        /**
    229         * soffice.binを起動します｡
    230         *
    231         * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
    232         * @og.rev 5.5.2.4 (2012/05/16) int priority は使われていないので､削除します｡
    233         *
    234         * @param envPath 環境変数ﾊﾟｽ
    235         * @param pipeName ﾊﾟｲﾌﾟ名
    236         *
    237         * @return soffice.binのﾌﾟﾛｾｽ
    238         */
    239        private Process execOffice( final String envPath, final String pipeName ) {
    240                String[] cmdArray = new String[11];
    241                cmdArray[0] = SOFFICE_BIN;
    242                cmdArray[1] = "-nologo";
    243                cmdArray[2] = "-nodefault";
    244                cmdArray[3] = "-norestore";
    245                cmdArray[4] = "-nocrashreport";
    246                cmdArray[5] = "-nolockcheck";
    247                cmdArray[6] = "-minimized";
    248                cmdArray[7] = "-invisible";
    249                cmdArray[8] = "-headless";
    250                cmdArray[9] = "-env:UserInstallation=file:///" + ( envPath ).replace( '\\', '/' );
    251                // 5.1.7.0 (2010/06/01) TCP接続対応
    252                cmdArray[10] = getProcParam( pipeName );
    253
    254                Process process;
    255                try {
    256                        process = Runtime.getRuntime().exec( cmdArray );
    257                } catch( final IOException ex ) {
    258                        throw new HybsSystemException( "[ERROR]PROCESS:Cant't exec soffice.bin", ex );
    259                }
    260
    261                return process;
    262        }
    263
    264        /**
    265         * Pipe名をｷｰにOpenOfficeのﾌﾟﾛｾｽを生成するためのﾊﾟﾗﾒｰﾀｰ文字列を生成します｡
    266         *
    267         * @param key Pipe名
    268         *
    269         * @return ﾌﾟﾛｾｽ生成ﾊﾟﾗﾒｰﾀｰ
    270         * @og.rtnNotNull
    271         */
    272        protected String getProcParam( final String key ) {
    273                return "-accept=pipe,name=" + key + ";urp;";
    274        }
    275
    276        /**
    277         * OOoの環境設定をﾁｪｯｸします｡
    278         *
    279         * ※ OFFICE_HOMEが設定されていない場合､HybsSystemException が､throw されます｡
    280         *
    281         * @og.rev 4.3.0.0 (2008/07/24) OS依存をやめてJavaでｺﾋﾟｰする
    282         *
    283         * @param envPath 環境設定のﾊﾟｽ
    284         */
    285        private void checkEnv( final String envPath ) {
    286
    287                if( OFFICE_HOME == null || OFFICE_HOME.isEmpty() ) {
    288                        throw new HybsSystemException( "OFFICE_HOMEが設定されていないため､OpenOfficeを起動できません" );
    289                }
    290
    291                // 4.3.0.0 (2008/07/24) OS依存からFileUtilを使うように変更
    292                FileUtil.copyDirectry( DEFAULT_ENV_PATH, envPath );
    293
    294                // 5.1.7.0 (2010/06/01) ﾌｧｲﾙﾏｰｼﾞ対応
    295                if( ! ( new File( getTempPath() ) ).mkdirs() ) {
    296                        System.err.println( "ﾌｧｲﾙﾏｰｼﾞ時のﾃﾝﾎﾟﾗﾘﾌｫﾙﾀﾞを作成できませんでした｡[" + getTempPath() + "]" );
    297                }
    298        }
    299
    300        /**
    301         * OpenOfficeのﾛｰｶﾙｺﾝﾎﾟｰﾈﾝﾄｺﾝﾃｷｽﾄを返します｡
    302         *
    303         * @og.rev 5.1.7.0 (2010/06/01) 新規作成
    304         * @og.rev 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
    305         *
    306         * @return ﾛｰｶﾙｺﾝﾎﾟｰﾈﾝﾄｺﾝﾃｷｽﾄ
    307         */
    308//      @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なｷｬｽﾄ警告の抑止｡ｷｬｽﾄをはずすと､旧3.1 では､ｴﾗｰになる｡
    309        public XDispatchHelper getDispatcher() {
    310                // 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
    311                if( remoteContext == null ) {
    312                        final String errMsg = "#bootstrap()を先に実行しておいてください｡" ;
    313                        throw new OgRuntimeException( errMsg );
    314                }
    315
    316                final XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
    317                XDispatchHelper dispatcher = null;
    318                try {
    319//                      dispatcher = (XDispatchHelper) UnoRuntime.queryInterface( XDispatchHelper.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.DispatchHelper", remoteContext ) );
    320                        dispatcher = UnoRuntime.queryInterface( XDispatchHelper.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.DispatchHelper", remoteContext ) );
    321                }
    322                catch( final com.sun.star.uno.Exception ex ) {
    323                        throw new HybsSystemException( "ﾃﾞｨｽﾊﾟｯﾁｬｰの取得に失敗しました｡", ex );
    324                }
    325                return dispatcher;
    326        }
    327
    328        /**
    329         * このﾌﾟﾛｾｽに対して固有に使用できる一時ﾌｧｲﾙのﾊﾟｽを指定します｡
    330         *
    331         * @og.rev 5.1.7.0 (2010/06/01) 新規作成
    332         *
    333         * @return 一時ﾌｧｲﾙのﾊﾟｽ
    334         * @og.rtnNotNull
    335         */
    336        public String getTempPath() {
    337//              return envPath + File.separator + "temp" + File.separator;
    338                return envPath + FS + "temp" + FS;              // 8.0.3.0 (2021/12/17)
    339        }
    340}