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 java.io.File;
019import java.io.IOException;
020
021import org.opengion.fukurou.util.FileUtil;
022import org.opengion.fukurou.util.StringUtil;
023import org.opengion.hayabusa.common.HybsSystem;
024import org.opengion.hayabusa.common.HybsSystemException;
025
026import com.sun.star.bridge.UnoUrlResolver;
027import com.sun.star.bridge.XUnoUrlResolver;
028import com.sun.star.comp.helper.Bootstrap;
029import com.sun.star.comp.helper.BootstrapException;
030import com.sun.star.frame.XDesktop;
031import com.sun.star.frame.XDispatchHelper;
032import com.sun.star.lang.XMultiComponentFactory;
033import com.sun.star.uno.UnoRuntime;
034import com.sun.star.uno.XComponentContext;
035
036/**
037 * OpenOfficeのプロセスを表すクラスです。
038 *
039 * bootstrap()メソッドが呼ばれたタイミングでsoffice.binのプロセスを生成します。
040 * soffice.binのプロセスを引数なしで実装した場合、通常は各ユーザーで1プロセスしか
041 * 生成されないため、-env:UserInstallationの引数を指定することで、仮想的に別ユーザー
042 * として起動しています。
043 * この"ユーザー"を表すキーは、コンストラクタの引数のidです。
044 *
045 * また、この仮想ユーザーで起動した場合、初回起動時にユーザー登録を促す画面が立ち上がります。
046 * これを回避するため、デフォルトの環境ファイルをプロセス生成前にコピーすることで、認証済みの
047 * 状態で立ち上がるようにしています。
048 *
049 * 起動したプロセスとの通知は名前付きパイプで行われます。パイプ名は、"env"+コンストラクタのidです。
050 * プロセス起動と、名前付きパイプでの接続は非同期で行われます。
051 * プロセス起動後、60秒経過しても接続できない場合は、BootstrapExceptionが発生します。
052 *
053 * @version  4.0
054 * @author   Hiroki Nakamura
055 * @since    JDK5.0,
056 */
057public class SOfficeProcess {
058
059        /** OOoのインストールディレクトリ  */
060        public static final String OFFICE_HOME =
061                new File( System.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;
062
063        /** 設定ファイルの雛形 */
064        private static final String DEFAULT_ENV_PATH =
065                OFFICE_HOME + "env" + File.separator + "_default";
066
067        /** soffice.binのパス */
068        private static final String SOFFICE_BIN =
069                OFFICE_HOME + File.separator + "program" + File.separator + "soffice.bin";
070
071        /** リモートデスクトップインスタンス */
072        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
073        private XDesktop desktop        = null;
074
075        private XComponentContext remoteContext = null;
076
077        /** soffice.binのプロセス */
078        private Process process         = null;
079
080        /** 環境設定のパス */
081        // 5.1.7.0 (2010/06/01) 複数サーバー対応漏れ
082        public static final String ENV_DIR = HybsSystem.url2dir( StringUtil.nval( HybsSystem.sys( "REPORT_FILE_URL" )
083                                                                                                                                                        , HybsSystem.sys( "FILE_URL" ) + "REPORT" + File.separator )
084                                                                                                                        + "oooenv" ) + File.separator;
085        private final String envPath;
086
087        /** 環境設定ファイルのID */
088        private final String envId;
089
090        /** ローカルコンテキスト */
091        private static XComponentContext xLocalContext = null;
092        static {
093                try {
094                        xLocalContext = Bootstrap.createInitialComponentContext( null );
095                }
096                catch( Throwable th ) {
097                        System.out.println( "[ERROR]OOo:Can't start LocalContext,Check OFFICE_HOME!" );
098                        th.printStackTrace();
099                }
100        }
101
102        /**
103         * コンストラクタです。
104         *
105         * @og.rev 4.3.0.0 (2008/07/15) 設定ファイルを各コンテキストごとに置くように変更
106         * @param       id      プロセスID
107         */
108        protected SOfficeProcess( final String id ) {
109                envId = id;
110                // envPath = OFFICE_HOME + "env" + File.separator + envId;
111                envPath = ENV_DIR + envId;
112        }
113
114        /**
115         * OOoへの接続を行います。
116         *
117         * @og.rev 5.0.0.0 (2009/08/03) Linux対応(パイプ名に":"が含まれていると接続できない)
118         * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
119         */
120        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
121        protected void bootstrap() {
122                System.out.println( "[INFO]OOo:Starting soffice process,ENV-ID=" + envId );
123
124                // check enviroment files, if no files, create from default files
125                checkEnv( envPath );
126
127                // pipe name
128                // 4.3.3.6 (2008/11/15) マルチサーバ対応。同一サーバでの複数実行時不具合のため。
129                // 5.0.0.0 (2009/08/03) Linux対応
130                //String sPipeName = "uno" + envId;
131                String sPipeName = "uno" + "_" + HybsSystem.sys("HOST_URL").replace(':','_').replace('/','_') + "_" + envId;
132
133                // start office process
134                // 5.5.2.4 (2012/05/16) int priority は使われていないので、削除します。
135                process = execOffice( envPath, sPipeName );
136                System.out.println( "[INFO]OOo:Invoke soffice.bin,ENV-ID=" + envId );
137
138                // create a URL resolver
139                XUnoUrlResolver xUrlResolver = UnoUrlResolver.create( xLocalContext );
140
141                // connection string
142                // 5.1.7.0 (2010/06/01) TCP接続対応
143                String sConnect = getConnParam( sPipeName );
144
145                // wait until office is started
146                try {
147                        for( int i = 0;; ++i ) {
148                                try {
149                                        Object context = xUrlResolver.resolve( sConnect );
150                                        remoteContext = (XComponentContext) UnoRuntime.queryInterface( XComponentContext.class, context );
151                                        if( remoteContext == null ) { throw new BootstrapException( "no component context!" ); }
152                                        break;
153                                }
154                                catch( com.sun.star.connection.NoConnectException ex ) {
155                                        System.out.println( "[INFO]OOo:Waiting for Connect soffice process,ENV-ID=" + envId );
156                                        if( i == 60 ) { throw new BootstrapException( ex ); }
157                                        Thread.sleep( 1000 );
158                                }
159                        }
160
161                        // create desktop instance
162                        XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
163                        desktop = (XDesktop) UnoRuntime.queryInterface( XDesktop.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.Desktop", remoteContext ) );
164                }
165                catch ( Exception ex ) {
166                        throw new HybsSystemException( "[ERROR] Can't create Desktop Instance", ex );
167                }
168
169                System.out.println( "[INFO]OOo:Connected successful,ENV-ID=" + envId );
170        }
171
172        /**
173         * Pipe名をキーにOpenOfficeのプロセスに接続するための文字列を生成します。
174         *
175         * @param key Pipe名
176         *
177         * @return 接続文字列
178         */
179        protected String getConnParam( final String key ) {
180                return "uno:pipe,name=" + key + ";urp;StarOffice.ComponentContext";
181        }
182
183        /**
184         * デスクトップインスタンスを返します
185         *
186         * @return デスクトップインスタンス
187         */
188        public XDesktop getDesktop() {
189                return desktop;
190        }
191
192        /**
193         * プロセスを終了します。
194         * また、同時に環境設定用のファイルも削除します。
195         */
196        public void close() {
197                process.destroy();
198                FileUtil.deleteFiles( new File( envPath ) );
199                System.out.println( "[INFO]OOo:Destroy process,ENV-ID=" + envId );
200        }
201
202        /**
203         * soffice.binを起動します。
204         *
205         * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
206         * @og.rev 5.5.2.4 (2012/05/16) int priority は使われていないので、削除します。
207         *
208         * @param envPath String
209         * @param pipeName String
210         *
211         * @return soffice.binのプロセス
212         */
213        private Process execOffice( final String envPath, final String pipeName ) {
214                String[] cmdArray = new String[11];
215                cmdArray[0] = SOFFICE_BIN;
216                cmdArray[1] = "-nologo";
217                cmdArray[2] = "-nodefault";
218                cmdArray[3] = "-norestore";
219                cmdArray[4] = "-nocrashreport";
220                cmdArray[5] = "-nolockcheck";
221                cmdArray[6] = "-minimized";
222                cmdArray[7] = "-invisible";
223                cmdArray[8] = "-headless";
224                cmdArray[9] = "-env:UserInstallation=file:///" + ( envPath ).replace( '\\', '/' );
225                // 5.1.7.0 (2010/06/01) TCP接続対応
226                cmdArray[10] = getProcParam( pipeName );
227
228                Process process;
229                try {
230                        process = Runtime.getRuntime().exec( cmdArray );
231                } catch ( IOException ex ) {
232                        throw new HybsSystemException( "[ERROR] Cant't exec soffice.bin", ex );
233                }
234
235                return process;
236        }
237
238        /**
239         * Pipe名をキーにOpenOfficeのプロセスを生成するためのパラメーター文字列を生成します。
240         *
241         * @param key Pipe名
242         *
243         * @return プロセス生成パラメーター
244         */
245        protected String getProcParam( final String key ) {
246                return "-accept=pipe,name=" + key + ";urp;";
247        }
248
249        /**
250         * OOoの環境設定ファイルをコピーします。
251         *
252         * ※ OFFICE_HOMEが設定されていない場合、HybsSystemException が、throw されます。
253         *
254         * @og.rev 4.3.0.0 (2008/07/24) OS依存をやめてJavaでコピーする
255         *
256         * @param envPath String
257         */
258        private void checkEnv( final String envPath ) {
259
260                if( OFFICE_HOME == null || OFFICE_HOME.length() == 0 ) {
261                        throw new HybsSystemException( "OFFICE_HOMEが設定されていないため、OpenOfficeを起動できません" );
262                }
263
264                // 4.3.0.0 (2008/07/24) OS依存からFileUtilを使うように変更
265                FileUtil.copyDirectry( DEFAULT_ENV_PATH, envPath );
266
267                // 5.1.7.0 (2010/06/01) ファイルマージ対応
268                if( ! ( new File( getTempPath() ) ).mkdirs() ) {
269                        System.err.println( "ファイルマージ時のテンポラリフォルダを作成できませんでした。[" + getTempPath() + "]" );
270                }
271        }
272
273        /**
274         * OpenOfficeのローカルコンポーネントコンテキストを返します。
275         *
276         * @og.rev 5.1.7.0 (2010/06/01) 新規作成
277         *
278         * @return ローカルコンポーネントコンテキスト
279         */
280        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
281        public XDispatchHelper getDispatcher() {
282                XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
283                XDispatchHelper dispatcher = null;
284                try {
285                        dispatcher = (XDispatchHelper) UnoRuntime.queryInterface( XDispatchHelper.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.DispatchHelper", remoteContext ) );
286                }
287                catch( com.sun.star.uno.Exception ex ) {
288                        throw new HybsSystemException( "ディスパッチャーの取得に失敗しました。", ex );
289                }
290                return dispatcher;
291        }
292
293        /**
294         * このプロセスに対して固有に使用できる一時ファイルのパスを指定します。
295         *
296         * @og.rev 5.1.7.0 (2010/06/01) 新規作成
297         *
298         * @return 一時ファイルのパス
299         */
300        public String getTempPath() {
301                return envPath + File.separator + "temp" + File.separator;
302        }
303}
304