001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.rmi;
029
030import org.opencms.file.CmsObject;
031import org.opencms.main.CmsException;
032import org.opencms.main.CmsLog;
033import org.opencms.main.CmsShell;
034import org.opencms.main.CmsShellCommandException;
035import org.opencms.main.I_CmsShellCommands;
036import org.opencms.main.OpenCms;
037
038import java.io.ByteArrayOutputStream;
039import java.io.PrintStream;
040import java.io.UnsupportedEncodingException;
041import java.rmi.RemoteException;
042import java.rmi.server.UnicastRemoteObject;
043import java.util.List;
044
045import org.apache.commons.lang3.RandomStringUtils;
046import org.apache.commons.logging.Log;
047
048import com.google.common.collect.Lists;
049
050/**
051 * RMI object which wraps a CmsShell and can be used for shell command execution.
052 */
053public class CmsRemoteShell extends UnicastRemoteObject implements I_CmsRemoteShell {
054
055    /**
056     * Stores remote shell instances which haven't been unregistered yet.<p>
057     */
058    static class InstanceStore {
059
060        /** The list of shell instances. */
061        private List<CmsRemoteShell> m_instances = Lists.newArrayList();
062
063        /**
064         * Adds a new instance.<p>
065         *
066         * @param shell the instance to add
067         */
068        public synchronized void add(CmsRemoteShell shell) {
069
070            m_instances.add(shell);
071        }
072
073        /**
074         * Removes and unexports an instance.<p>
075         *
076         * @param cmsRemoteShell the instance to remove
077         */
078        @SuppressWarnings("synthetic-access")
079        public synchronized void remove(CmsRemoteShell cmsRemoteShell) {
080
081            try {
082                UnicastRemoteObject.unexportObject(cmsRemoteShell, true);
083            } catch (Exception e) {
084                LOG.error(e.getLocalizedMessage(), e);
085            }
086            m_instances.remove(cmsRemoteShell);
087        }
088
089        /**
090         * Removes and unexports all instances.<p>
091         */
092        @SuppressWarnings("synthetic-access")
093        public synchronized void removeAll() {
094
095            for (CmsRemoteShell shell : m_instances) {
096                try {
097                    UnicastRemoteObject.unexportObject(shell, true);
098                } catch (Exception e) {
099                    LOG.error(e.getLocalizedMessage(), e);
100                }
101            }
102            m_instances.clear();
103
104        }
105
106    }
107
108    /** The log instance for this class. */
109    private static final Log LOG = CmsLog.getLog(CmsRemoteShell.class);
110
111    /** Serial version id. */
112    private static final long serialVersionUID = -243325251951003282L;
113
114    /** Stores instances which have yet to be unexported. */
115    private static InstanceStore m_instanceStore = new InstanceStore();
116
117    /** Byte array stream used to capture output of shell commands; will be cleared for each individual command. */
118    private ByteArrayOutputStream m_baos = new ByteArrayOutputStream();
119
120    /** Random id string for debugging purposes. */
121    private String m_id;
122
123    /** The output stream used to capture the shell command output. */
124    private PrintStream m_out;
125
126    /** The wrapped shell instance. */
127    private CmsShell m_shell;
128
129    /**
130     * Creates a new instance.<p>
131     *
132     * @param additionalCommandsName a class name for an additional shell commands class (may be null)
133     * @param port the port to use
134     *
135     * @throws CmsException if something goes wrong
136     * @throws RemoteException if RMI stuff goes wrong
137     */
138    public CmsRemoteShell(String additionalCommandsName, int port)
139    throws CmsException, RemoteException {
140
141        super(port);
142        m_id = RandomStringUtils.randomAlphanumeric(8);
143        I_CmsShellCommands additionalCommands = null;
144        if (additionalCommandsName != null) {
145            try {
146                Class<?> commandsCls = Class.forName(additionalCommandsName);
147                if (I_CmsShellCommands.class.isAssignableFrom(commandsCls)) {
148                    additionalCommands = (I_CmsShellCommands)(commandsCls.newInstance());
149                }
150            } catch (Exception e) {
151                LOG.error(e.getLocalizedMessage(), e);
152                throw new IllegalArgumentException(
153                    "Could not create command class instance for " + additionalCommandsName,
154                    e);
155            }
156        }
157
158        CmsObject cms = OpenCms.initCmsObject("Guest");
159        m_out = new PrintStream(m_baos, true);
160        m_shell = new CmsShell(cms, "${user}@${project}:${siteroot}|${uri}>", additionalCommands, m_out, m_out);
161        m_instanceStore.add(this);
162    }
163
164    /**
165     * Removes and unexports all instances.<p>
166     */
167    public static void unregisterAll() {
168
169        m_instanceStore.removeAll();
170    }
171
172    /**
173     * @see org.opencms.rmi.I_CmsRemoteShell#end()
174     */
175    public void end() {
176
177        m_instanceStore.remove(this);
178    }
179
180    /**
181     * @see org.opencms.rmi.I_CmsRemoteShell#executeCommand(java.lang.String, java.util.List)
182     */
183    public CmsShellCommandResult executeCommand(String cmd, List<String> params) {
184
185        LOG.debug(m_id + " executing " + cmd + " " + params);
186        CmsShellCommandResult result = new CmsShellCommandResult();
187        m_baos.reset();
188        boolean hasError = false;
189        try {
190            CmsShell.pushShell(m_shell);
191            m_shell.executeCommand(cmd, params);
192        } catch (CmsShellCommandException e) {
193            hasError = true;
194            LOG.warn(m_id + " " + e.getLocalizedMessage(), e);
195        } finally {
196            CmsShell.popShell();
197            m_out.flush();
198        }
199        hasError |= m_shell.hasReportError();
200        result.setExitCalled(m_shell.isExitCalled());
201        result.setHasError(hasError);
202        result.setErrorCode(m_shell.getErrorCode());
203        result.setPrompt(m_shell.getPrompt());
204        result.setEcho(m_shell.hasEcho());
205        try {
206            String outputString = new String(m_baos.toByteArray(), "UTF-8");
207            result.setOutput(outputString);
208        } catch (UnsupportedEncodingException e) {
209            e.printStackTrace();
210        }
211        return result;
212    }
213
214    /**
215     * @see org.opencms.rmi.I_CmsRemoteShell#getPrompt()
216     */
217    public String getPrompt() {
218
219        return m_shell.getPrompt();
220    }
221
222}