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.setup.ui;
029
030import org.opencms.setup.CmsSetupBean;
031import org.opencms.setup.CmsSetupDb;
032import org.opencms.ui.CmsVaadinUtils;
033import org.opencms.util.CmsStringUtil;
034
035import java.util.ArrayList;
036import java.util.List;
037import java.util.Map;
038import java.util.Properties;
039
040import com.vaadin.ui.Button;
041import com.vaadin.ui.ComboBox;
042import com.vaadin.ui.VerticalLayout;
043
044/**
045 * Setup step: Databbase settings.
046 */
047public class CmsSetupStep03Database extends A_CmsSetupStep {
048
049    /**
050     * Exception class for use during DB setup, where we get lists of strings as errors from CmsSetupDb instead of the original exceptions.
051     */
052    class DBException extends RuntimeException {
053
054        /** The list of original errors. */
055        private List<String> m_errors;
056
057        /** The error message. */
058        private String m_message;
059
060        /**
061         * Creates a new instance.<p>
062         *
063         * @param message the error message
064         * @param errors the list of original errors
065         */
066        public DBException(String message, List<String> errors) {
067
068            m_message = message;
069            m_errors = new ArrayList<>(errors);
070
071        }
072
073        /**
074         * Gets original errors, separated by newlines.
075         *
076         * @return the original errors
077         */
078        public String getDetails() {
079
080            return CmsStringUtil.listAsString(m_errors, "\n");
081        }
082
083        /**
084         * @see java.lang.Throwable#getMessage()
085         */
086        @Override
087        public String getMessage() {
088
089            return m_message;
090        }
091    }
092
093    /** Back button. */
094    private Button m_backButton;
095
096    /** DB selector. */
097    private ComboBox m_dbSelect;
098
099    /** Forward button. */
100    private Button m_forwardButton;
101
102    /** Main layout. */
103    private VerticalLayout m_mainLayout;
104
105    /** Array for storing the DB settings panel (need to wrap it in array because it's not part of the declarative layout). */
106    private CmsDbSettingsPanel[] m_panel = {null};
107
108    /** Setup bean. */
109    private CmsSetupBean m_setupBean;
110
111    /**
112     * Creates a new instance.
113     *
114     * @param context the setup context
115     */
116    public CmsSetupStep03Database(I_SetupUiContext context) {
117
118        super(context);
119        CmsVaadinUtils.readAndLocalizeDesign(this, null, null);
120        m_setupBean = context.getSetupBean();
121        Map<String, Properties> propsForDbs = m_setupBean.getDatabaseProperties();
122        List<String> dbList = new ArrayList<>();
123
124        for (Map.Entry<String, Properties> entry : propsForDbs.entrySet()) {
125            dbList.add(entry.getKey());
126        }
127        m_dbSelect.setItems(dbList);
128        m_dbSelect.setItemCaptionGenerator(db -> propsForDbs.get(db).get(db + ".name").toString());
129        String path = context.getSetupBean().getServletConfig().getServletContext().getContextPath();
130        int lastSlash = path.lastIndexOf("/");
131        String webapp = null;
132        if (lastSlash != -1) {
133            String lastSegment = path.substring(lastSlash + 1);
134            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(lastSegment)) {
135                webapp = lastSegment;
136            }
137        }
138        final String fWebapp = webapp;
139        m_dbSelect.addValueChangeListener(evt -> {
140            String value = (String)(evt.getValue());
141            updateDb(value, fWebapp);
142        });
143        m_dbSelect.setNewItemProvider(null);
144        m_dbSelect.setEmptySelectionAllowed(false);
145        m_dbSelect.setValue("mysql");
146        m_forwardButton.addClickListener(evt -> forward());
147        m_backButton.addClickListener(evt -> m_context.stepBack());
148    }
149
150    /**
151     * Creates DB and tables when necessary.<p>
152     *
153     * @throws Exception in case creating DB or tables fails
154     */
155    public void setupDb(boolean createDb, boolean createTables, boolean dropDb) throws Exception {
156
157        boolean dbExists = false;
158        if (m_setupBean.isInitialized()) {
159            System.out.println("Setup-Bean initialized successfully.");
160            CmsSetupDb db = new CmsSetupDb(m_setupBean.getWebAppRfsPath());
161            try {
162                // try to connect as the runtime user
163                System.out.println("Check runtime connection....");
164                db.setConnection(
165                    m_setupBean.getDbDriver(),
166                    m_setupBean.getDbWorkConStr(),
167                    m_setupBean.getDbConStrParams(),
168                    m_setupBean.getDbWorkUser(),
169                    m_setupBean.getDbWorkPwd(),
170                    false);
171                System.out.println("Check runtime connection - COMPLETED");
172                if (!db.noErrors()) {
173                    System.out.println("Check setup connection....");
174                    // try to connect as the setup user
175                    db.closeConnection();
176                    db.clearErrors();
177                    db.setConnection(
178                        m_setupBean.getDbDriver(),
179                        m_setupBean.getDbCreateConStr(),
180                        m_setupBean.getDbConStrParams(),
181                        m_setupBean.getDbCreateUser(),
182                        m_setupBean.getDbCreatePwd());
183                    System.out.println("Check setup connection - COMPLETED");
184                } else {
185                    dbExists = true;
186                }
187                if (!db.noErrors()) {
188                    throw new DBException("DB connection test failed.", db.getErrors());
189                }
190            } finally {
191                db.clearErrors();
192                db.closeConnection();
193            }
194        }
195
196        System.out.println("DB connection tested successfully.");
197
198        CmsSetupDb db = null;
199        if (m_setupBean.isInitialized()) {
200            if (createDb || createTables) {
201                db = new CmsSetupDb(m_setupBean.getWebAppRfsPath());
202                // check if database exists
203                if (m_setupBean.getDatabase().startsWith("oracle")
204                    || m_setupBean.getDatabase().startsWith("db2")
205                    || m_setupBean.getDatabase().startsWith("as400")) {
206                    setWorkConnection(db);
207                } else {
208                    db.setConnection(
209                        m_setupBean.getDbDriver(),
210                        m_setupBean.getDbWorkConStr(),
211                        m_setupBean.getDbConStrParams(),
212                        m_setupBean.getDbCreateUser(),
213                        m_setupBean.getDbCreatePwd(),
214                        false);
215                    dbExists = db.noErrors();
216                    if (dbExists) {
217                        db.closeConnection();
218                    } else {
219                        db.clearErrors();
220                    }
221                }
222                if (!dbExists || dropDb) {
223                    db.closeConnection();
224                    if (!m_setupBean.getDatabase().startsWith("db2")
225                        && !m_setupBean.getDatabase().startsWith("as400")) {
226                        db.setConnection(
227                            m_setupBean.getDbDriver(),
228                            m_setupBean.getDbCreateConStr(),
229                            m_setupBean.getDbConStrParams(),
230                            m_setupBean.getDbCreateUser(),
231                            m_setupBean.getDbCreatePwd());
232                    }
233                }
234            }
235        }
236        if (!createDb && !createTables && !dbExists) {
237            throw new Exception("You have not created the Alkacon OpenCms database.");
238        }
239        if (dbExists && createTables && !dropDb && (db != null)) {
240            throw new Exception("You have selected to not drop existing DBs, but a DB with the given name exists.");
241        }
242        if (dbExists && createDb && dropDb && (db != null)) {
243            // drop the DB
244            db.closeConnection();
245            db.setConnection(
246                m_setupBean.getDbDriver(),
247                m_setupBean.getDbCreateConStr(),
248                m_setupBean.getDbConStrParams(),
249                m_setupBean.getDbCreateUser(),
250                m_setupBean.getDbCreatePwd());
251            db.dropDatabase(m_setupBean.getDatabase(), m_setupBean.getReplacer());
252            if (!db.noErrors()) {
253                List<String> errors = new ArrayList<>(db.getErrors());
254                db.clearErrors();
255                throw new DBException("Error occurred while dropping the DB!", errors);
256            }
257            System.out.println("Database dropped successfully.");
258        }
259
260        if (createDb && (db != null)) {
261            // Create Database
262            db.createDatabase(m_setupBean.getDatabase(), m_setupBean.getReplacer());
263            if (!db.noErrors()) {
264                DBException ex = new DBException("Error occurred while creating the DB!", db.getErrors());
265                db.clearErrors();
266                throw ex;
267            }
268            db.closeConnection();
269            System.out.println("Database created successfully.");
270        }
271
272        if (createTables && (db != null)) {
273            setWorkConnection(db);
274            //Drop Tables (intentionally quiet)
275            db.dropTables(m_setupBean.getDatabase());
276            db.clearErrors();
277            db.closeConnection();
278            // reopen the connection in order to display errors
279            setWorkConnection(db);
280            //Create Tables
281            db.createTables(m_setupBean.getDatabase(), m_setupBean.getReplacer());
282            if (!db.noErrors()) {
283                DBException ex = new DBException("Error occurred while creating the DB!", db.getErrors());
284                db.clearErrors();
285                throw ex;
286            }
287            db.closeConnection();
288            System.out.println("Tables created successfully.");
289        }
290        if (db != null) {
291            db.closeConnection();
292        }
293        System.out.println("Database setup was successful.");
294        m_context.stepForward();
295    }
296
297    /**
298     * Set work connection.
299     *
300     * @param db the db setup bean
301     */
302    public void setWorkConnection(CmsSetupDb db) {
303
304        db.setConnection(
305            m_setupBean.getDbDriver(),
306            m_setupBean.getDbWorkConStr(),
307            m_setupBean.getDbConStrParams(),
308            m_setupBean.getDbWorkUser(),
309            m_setupBean.getDbWorkPwd());
310    }
311
312    /**
313     * Proceed to next step.
314     */
315    private void forward() {
316
317        try {
318            CmsDbSettingsPanel panel = m_panel[0];
319            panel.saveToSetupBean();
320
321            boolean createDb = panel.getCreateDb();
322            boolean dropDb = panel.getDropDb();
323            boolean createTables = panel.getCreateTables();
324            setupDb(createDb, createTables, dropDb);
325        } catch (DBException e) {
326            CmsSetupErrorDialog.showErrorDialog(e.getMessage(), e.getDetails());
327        } catch (Exception e) {
328            CmsSetupErrorDialog.showErrorDialog(e);
329        }
330
331    }
332
333    /**
334    * Switches DB type.
335    *
336    * @param dbName the database type
337    * @param webapp the webapp name
338    */
339    private void updateDb(String dbName, String webapp) {
340
341        m_mainLayout.removeAllComponents();
342        m_setupBean.setDatabase(dbName);
343        CmsDbSettingsPanel panel = new CmsDbSettingsPanel(m_setupBean);
344        m_panel[0] = panel;
345        panel.initFromSetupBean(webapp);
346        m_mainLayout.addComponent(panel);
347    }
348
349}