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 GmbH & Co. KG, 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.workplace.tools;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsResource;
033import org.opencms.i18n.CmsEncoder;
034import org.opencms.jsp.CmsJspActionElement;
035import org.opencms.main.CmsException;
036import org.opencms.main.CmsLog;
037import org.opencms.util.CmsRequestUtil;
038import org.opencms.util.CmsStringUtil;
039import org.opencms.workplace.CmsDialog;
040import org.opencms.workplace.CmsWorkplace;
041
042import java.io.IOException;
043import java.util.ArrayList;
044import java.util.HashMap;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Map;
048
049import javax.servlet.ServletException;
050
051import org.apache.commons.logging.Log;
052
053/**
054 * Manages the registered tools, actualizing its state every time the workplace is reinitialize.<p>
055 *
056 * Manages also the configuration settings for the administration view, and provides
057 * several tool related methods.<p>
058 *
059 * @since 6.0.0
060 */
061public class CmsToolManager {
062
063    /**  Root location of the administration view. */
064    public static final String ADMINVIEW_ROOT_LOCATION = CmsWorkplace.PATH_WORKPLACE + "views/admin";
065
066    /**  Property definition name to look for. */
067    public static final String HANDLERCLASS_PROPERTY = "admintoolhandler-class";
068
069    /**  Navigation bar separator (html code). */
070    public static final String NAVBAR_SEPARATOR = "\n&nbsp;&gt;&nbsp;\n";
071
072    /**  Tool root separator. */
073    public static final String ROOT_SEPARATOR = ":";
074
075    /**  Key for the default tool root, if there is no configured root with this a key, a new one will be configured. */
076    public static final String ROOTKEY_DEFAULT = "admin";
077
078    /**  Tool path separator. */
079    public static final String TOOLPATH_SEPARATOR = "/";
080
081    /** Location of the default admin view jsp page. */
082    public static final String VIEW_JSPPAGE_LOCATION = ADMINVIEW_ROOT_LOCATION + "/admin-main.jsp";
083
084    /** The static log object for this class. */
085    private static final Log LOG = CmsLog.getLog(CmsToolManager.class);
086
087    /** List of all available roots. */
088    private final CmsIdentifiableObjectContainer<CmsToolRootHandler> m_roots;
089
090    /** List of all available tools. */
091    private final CmsIdentifiableObjectContainer<CmsTool> m_tools;
092
093    /** List of all available urls and related tool paths. */
094    private final CmsIdentifiableObjectContainer<String> m_urls;
095
096    /**
097     * Default constructor.<p>
098     */
099    public CmsToolManager() {
100
101        m_roots = new CmsIdentifiableObjectContainer<CmsToolRootHandler>(true, false);
102        m_tools = new CmsIdentifiableObjectContainer<CmsTool>(true, false);
103        m_urls = new CmsIdentifiableObjectContainer<String>(false, false);
104    }
105
106    /**
107     * Returns the OpenCms link for the given tool path which requires no parameters.<p>
108     *
109     * @param jsp the jsp action element
110     * @param toolPath the tool path
111     *
112     * @return the OpenCms link for the given tool path which requires parameters
113     */
114    public static String linkForToolPath(CmsJspActionElement jsp, String toolPath) {
115
116        StringBuffer result = new StringBuffer();
117        result.append(jsp.link(VIEW_JSPPAGE_LOCATION));
118        result.append('?');
119        result.append(CmsToolDialog.PARAM_PATH);
120        result.append('=');
121        result.append(CmsEncoder.encode(toolPath));
122        return result.toString();
123    }
124
125    /**
126     * Returns the OpenCms link for the given tool path which requires parameters.<p>
127     *
128     * Please note: Don't overuse the parameter map because this will likely introduce issues
129     * with encoding. If possible, don't pass parameters at all, or only very simple parameters
130     * with no special chars that can easily be parsed.<p>
131     *
132     * @param jsp the jsp action element
133     * @param toolPath the tool path
134     * @param params the map of required tool parameters
135     *
136     * @return the OpenCms link for the given tool path which requires parameters
137     */
138    public static String linkForToolPath(CmsJspActionElement jsp, String toolPath, Map<String, String[]> params) {
139
140        if (params == null) {
141            // no parameters - take the shortcut
142            return linkForToolPath(jsp, toolPath);
143        }
144        params.put(CmsToolDialog.PARAM_PATH, new String[] {toolPath});
145        return CmsRequestUtil.appendParameters(jsp.link(VIEW_JSPPAGE_LOCATION), params, true);
146    }
147
148    /**
149     * Adds a new tool root to the tool manager.<p>
150     *
151     * @param toolRoot the tool root to add
152     */
153    public void addToolRoot(CmsToolRootHandler toolRoot) {
154
155        m_roots.addIdentifiableObject(toolRoot.getKey(), toolRoot);
156    }
157
158    /**
159     * Called by the <code>{@link org.opencms.workplace.CmsWorkplaceManager#initialize(CmsObject)}</code> method.<p>
160     *
161     * @param cms the admin cms context
162     */
163    public void configure(CmsObject cms) {
164
165        if (CmsLog.INIT.isInfoEnabled()) {
166            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_TOOLMANAGER_CREATED_0));
167        }
168        if (m_roots.getObject(ROOTKEY_DEFAULT) == null) {
169            CmsToolRootHandler defToolRoot = new CmsToolRootHandler();
170            defToolRoot.setKey(ROOTKEY_DEFAULT);
171            defToolRoot.setUri(CmsWorkplace.PATH_WORKPLACE + "admin/");
172            defToolRoot.setName("${key." + Messages.GUI_ADMIN_VIEW_ROOT_NAME_0 + "}");
173            defToolRoot.setHelpText("${key." + Messages.GUI_ADMIN_VIEW_ROOT_HELP_0 + "}");
174            addToolRoot(defToolRoot);
175        }
176        m_tools.clear();
177        m_urls.clear();
178        Iterator<CmsToolRootHandler> it = getToolRoots().iterator();
179        while (it.hasNext()) {
180            CmsToolRootHandler toolRoot = it.next();
181            if (!cms.existsResource(toolRoot.getUri())) {
182                if (CmsLog.INIT.isInfoEnabled()) {
183                    CmsLog.INIT.info(
184                        Messages.get().getBundle().key(
185                            Messages.INIT_TOOLMANAGER_ROOT_SKIPPED_2,
186                            toolRoot.getKey(),
187                            toolRoot.getUri()));
188                }
189                continue;
190            }
191            try {
192                toolRoot.setup(cms, null, toolRoot.getUri());
193                configureToolRoot(cms, toolRoot);
194                // log info
195                if (CmsLog.INIT.isInfoEnabled()) {
196                    CmsLog.INIT.info(
197                        Messages.get().getBundle().key(Messages.INIT_TOOLMANAGER_SETUP_1, toolRoot.getKey()));
198                }
199            } catch (CmsException e) {
200                // log failure
201                if (CmsLog.INIT.isWarnEnabled()) {
202                    CmsLog.INIT.warn(
203                        Messages.get().getBundle().key(Messages.INIT_TOOLMANAGER_SETUP_ERROR_1, toolRoot.getKey()),
204                        e);
205                }
206            }
207        }
208    }
209
210    /**
211     * Returns the navigation bar html code for the given tool path.<p>
212     *
213     * @param toolPath the path
214     * @param wp the jsp page
215     *
216     * @return the html code
217     */
218    public String generateNavBar(String toolPath, CmsWorkplace wp) {
219
220        if (toolPath.equals(getBaseToolPath(wp))) {
221            return "<div class='pathbar'>&nbsp;</div>\n";
222        }
223        CmsTool adminTool = resolveAdminTool(getCurrentRoot(wp).getKey(), toolPath);
224        String html = A_CmsHtmlIconButton.defaultButtonHtml(
225            CmsHtmlIconButtonStyleEnum.SMALL_ICON_TEXT,
226            "nav" + adminTool.getId(),
227            adminTool.getHandler().getName(),
228            null,
229            false,
230            null,
231            null,
232            null);
233        String parent = toolPath;
234        while (!parent.equals(getBaseToolPath(wp))) {
235            parent = getParent(wp, parent);
236            adminTool = resolveAdminTool(getCurrentRoot(wp).getKey(), parent);
237            if (adminTool == null) {
238                break;
239            }
240            String id = "nav" + adminTool.getId();
241            String link = linkForToolPath(wp.getJsp(), parent, adminTool.getHandler().getParameters(wp));
242            String onClic = "openPage('" + link + "');";
243            String buttonHtml = A_CmsHtmlIconButton.defaultButtonHtml(
244                CmsHtmlIconButtonStyleEnum.SMALL_ICON_TEXT,
245                id,
246                adminTool.getHandler().getName(),
247                adminTool.getHandler().getHelpText(),
248                true,
249                null,
250                null,
251                onClic);
252            html = "<span>" + buttonHtml + NAVBAR_SEPARATOR + "</span>" + html;
253        }
254        html = CmsToolMacroResolver.resolveMacros(html, wp);
255        html = CmsEncoder.decode(html);
256        html = CmsToolMacroResolver.resolveMacros(html, wp);
257        html = "<div class='pathbar'>\n" + html + "</div>\n";
258        return html;
259    }
260
261    /**
262     * Returns the base tool path for the active user.<p>
263     *
264     * @param wp the workplace object
265     *
266     * @return the base tool path for the active user
267     */
268    public String getBaseToolPath(CmsWorkplace wp) {
269
270        CmsToolUserData userData = getUserData(wp);
271        String path = TOOLPATH_SEPARATOR;
272        if (userData != null) {
273            path = userData.getBaseTool(getCurrentRoot(wp).getKey());
274        }
275        return path;
276    }
277
278    /**
279     * Returns the current user's root handler.<p>
280     *
281     * @param wp the workplace context
282     *
283     * @return the current user's root handler
284     */
285    public CmsToolRootHandler getCurrentRoot(CmsWorkplace wp) {
286
287        CmsToolUserData userData = getUserData(wp);
288        String root = ROOTKEY_DEFAULT;
289        if (userData != null) {
290            if (m_roots.getObject(userData.getRootKey()) != null) {
291                root = userData.getRootKey();
292            } else {
293                if (LOG.isErrorEnabled()) {
294                    LOG.error(
295                        Messages.get().getBundle().key(Messages.ERR_NOT_CONFIGURED_ROOT_1, userData.getRootKey()));
296                }
297            }
298        }
299        return m_roots.getObject(root);
300    }
301
302    /**
303     * Returns the current tool.<p>
304     *
305     * @param wp the workplace object
306     *
307     * @return the current tool
308     */
309    public CmsTool getCurrentTool(CmsWorkplace wp) {
310
311        return resolveAdminTool(getCurrentRoot(wp).getKey(), getCurrentToolPath(wp));
312    }
313
314    /**
315     * Returns the current tool path.<p>
316     *
317     * @param wp the workplace object
318     *
319     * @return the current tool path
320     */
321    public String getCurrentToolPath(CmsWorkplace wp) {
322
323        CmsToolUserData userData = getUserData(wp);
324        String path = getBaseToolPath(wp);
325        if (userData != null) {
326            path = userData.getCurrentToolPath(getCurrentRoot(wp).getKey());
327        }
328        return path;
329    }
330
331    /**
332     * Returns the path to the parent of the tool identified by the given tool path.<p>
333     *
334     * The parent of the root is the same root.<p>
335     *
336     * @param wp the workplace object
337     * @param toolPath the abstract tool path
338     *
339     * @return his parent
340     */
341    public String getParent(CmsWorkplace wp, String toolPath) {
342
343        if (toolPath.equals(getBaseToolPath(wp))) {
344            return toolPath;
345        }
346        int pos = toolPath.lastIndexOf(TOOLPATH_SEPARATOR);
347        return pos <= 0 ? TOOLPATH_SEPARATOR : toolPath.substring(0, pos);
348    }
349
350    /**
351     * Returns a list with all registered tools.<p>
352     *
353     * @return list if <code>{@link CmsTool}</code>
354     */
355    public List<CmsTool> getToolHandlers() {
356
357        return m_tools.elementList();
358    }
359
360    /**
361     * Returns a list of tool roots.<p>
362     *
363     * @return a list of {@link CmsToolRootHandler} objects
364     */
365    public List<CmsToolRootHandler> getToolRoots() {
366
367        return m_roots.elementList();
368    }
369
370    /**
371     * Returns a list of all tools in the given path.<p>
372     *
373     * @param wp the workplace context
374     * @param baseTool the path
375     * @param includeSubtools if the tools in subfolders should be also returned
376     *
377     * @return a list of {@link CmsTool} objects
378     */
379    public List<CmsTool> getToolsForPath(CmsWorkplace wp, String baseTool, boolean includeSubtools) {
380
381        List<CmsTool> toolList = new ArrayList<CmsTool>();
382        String rootKey = getCurrentRoot(wp).getKey();
383        Iterator<CmsTool> itTools = m_tools.elementList().iterator();
384        while (itTools.hasNext()) {
385            CmsTool tool = itTools.next();
386            String path = tool.getHandler().getPath();
387            if (resolveAdminTool(rootKey, path) != tool) {
388                continue;
389            }
390            if (path.equals(TOOLPATH_SEPARATOR)) {
391                continue;
392            }
393            // leave out everything above the base
394            if (!path.startsWith(baseTool)) {
395                continue;
396            }
397            // filter for path
398            if (baseTool.equals(TOOLPATH_SEPARATOR) || path.startsWith(baseTool + TOOLPATH_SEPARATOR)) {
399                // filter sub tree
400                if (includeSubtools || (path.indexOf(TOOLPATH_SEPARATOR, baseTool.length() + 1) < 0)) {
401                    toolList.add(tool);
402                }
403            }
404        }
405        return toolList;
406    }
407
408    /**
409     * Returns the <code>{@link CmsToolUserData}</code> object for a given user.<p>
410     *
411     * @param wp the workplace object
412     *
413     * @return the current user data
414     */
415    public CmsToolUserData getUserData(CmsWorkplace wp) {
416
417        CmsToolUserData userData = wp.getSettings().getToolUserData();
418        if (userData == null) {
419            userData = new CmsToolUserData();
420            userData.setRootKey(ROOTKEY_DEFAULT);
421            Iterator<CmsToolRootHandler> it = getToolRoots().iterator();
422            while (it.hasNext()) {
423                CmsToolRootHandler root = it.next();
424                userData.setCurrentToolPath(root.getKey(), TOOLPATH_SEPARATOR);
425                userData.setBaseTool(root.getKey(), TOOLPATH_SEPARATOR);
426            }
427            wp.getSettings().setToolUserData(userData);
428        }
429        return userData;
430    }
431
432    /**
433     * Returns <code>true</code> if there is at least one tool registered using the given url.<p>
434     *
435     * @param url the url of the tool
436     *
437     * @return <code>true</code> if there is at least one tool registered using the given url
438     */
439    public boolean hasToolPathForUrl(String url) {
440
441        List<String> toolPaths = m_urls.getObjectList(url);
442        return ((toolPaths != null) && !toolPaths.isEmpty());
443    }
444
445    /**
446     * This method initializes the tool manager for the current user.<p>
447     *
448     * @param wp the jsp page coming from
449     */
450    public synchronized void initParams(CmsToolDialog wp) {
451
452        setCurrentRoot(wp, wp.getParamRoot());
453        setCurrentToolPath(wp, wp.getParamPath());
454        setBaseToolPath(wp, wp.getParamBase());
455
456        // if the current tool path is not under the current root, set the current root as the current tool
457        if (!getCurrentToolPath(wp).startsWith(getBaseToolPath(wp))) {
458            setCurrentToolPath(wp, getBaseToolPath(wp));
459        }
460        wp.setParamPath(getCurrentToolPath(wp));
461        wp.setParamBase(getBaseToolPath(wp));
462        wp.setParamRoot(getCurrentRoot(wp).getKey());
463    }
464
465    /**
466     * Redirects to the given page with the given parameters.<p>
467     *
468     * @param wp the workplace object
469     * @param pagePath the path to the page to redirect to
470     * @param params the parameters to send
471     *
472     * @throws IOException in case of errors during forwarding
473     * @throws ServletException in case of errors during forwarding
474     */
475    public void jspForwardPage(CmsWorkplace wp, String pagePath, Map<String, String[]> params)
476    throws IOException, ServletException {
477
478        Map<String, String[]> newParams = createToolParams(wp, pagePath, params);
479        if (pagePath.indexOf("?") > 0) {
480            pagePath = pagePath.substring(0, pagePath.indexOf("?"));
481        }
482
483        wp.setForwarded(true);
484        // forward to the requested page uri
485        CmsRequestUtil.forwardRequest(
486            wp.getJsp().link(pagePath),
487            CmsRequestUtil.createParameterMap(newParams),
488            wp.getJsp().getRequest(),
489            wp.getJsp().getResponse());
490    }
491
492    /**
493     * Redirects to the given tool with the given parameters.<p>
494     *
495     * @param wp the workplace object
496     * @param toolPath the path to the tool to redirect to
497     * @param params the parameters to send
498     *
499     * @throws IOException in case of errors during forwarding
500     * @throws ServletException in case of errors during forwarding
501     */
502    public void jspForwardTool(CmsWorkplace wp, String toolPath, Map<String, String[]> params)
503    throws IOException, ServletException {
504
505        Map<String, String[]> newParams;
506        if (params == null) {
507            newParams = new HashMap<String, String[]>();
508        } else {
509            newParams = new HashMap<String, String[]>(params);
510        }
511        // update path param
512        newParams.put(CmsToolDialog.PARAM_PATH, new String[] {toolPath});
513        jspForwardPage(wp, VIEW_JSPPAGE_LOCATION, newParams);
514    }
515
516    /**
517     * Returns the admin tool corresponding to the given abstract path.<p>
518     *
519     * @param rootKey the tool root
520     * @param toolPath the path
521     *
522     * @return the corresponding tool, or <code>null</code> if not found
523     */
524    public CmsTool resolveAdminTool(String rootKey, String toolPath) {
525
526        return m_tools.getObject(rootKey + ROOT_SEPARATOR + toolPath);
527    }
528
529    /**
530     * Sets the base tool path.<p>
531     *
532     * @param wp the workplace object
533     * @param baseToolPath the base tool path to set
534     */
535    public void setBaseToolPath(CmsWorkplace wp, String baseToolPath) {
536
537        // use last used base if param empty
538        if (CmsStringUtil.isEmpty(baseToolPath) || baseToolPath.trim().equals("null")) {
539            baseToolPath = getBaseToolPath(wp);
540        }
541        baseToolPath = repairPath(wp, baseToolPath);
542        // set it
543        CmsToolUserData userData = getUserData(wp);
544        userData.setBaseTool(userData.getRootKey(), baseToolPath);
545    }
546
547    /**
548     * Sets the current user's root key.<p>
549     *
550     * @param wp the workplace context
551     * @param key the current user's root key to set
552     */
553    public void setCurrentRoot(CmsWorkplace wp, String key) {
554
555        // use last used root if param empty
556        if (CmsStringUtil.isEmpty(key) || key.trim().equals("null")) {
557            key = getCurrentRoot(wp).getKey();
558        }
559        // set it
560        CmsToolUserData userData = getUserData(wp);
561        userData.setRootKey(key);
562    }
563
564    /**
565     * Sets the current tool path.<p>
566     *
567     * @param wp the workplace object
568     * @param currentToolPath the current tool path to set
569     */
570    public void setCurrentToolPath(CmsWorkplace wp, String currentToolPath) {
571
572        // use last used path if param empty
573        if (CmsStringUtil.isEmptyOrWhitespaceOnly(currentToolPath) || currentToolPath.trim().equals("null")) {
574            currentToolPath = getCurrentToolPath(wp);
575        }
576        currentToolPath = repairPath(wp, currentToolPath);
577        // use it
578        CmsToolUserData userData = getUserData(wp);
579        userData.setCurrentToolPath(userData.getRootKey(), currentToolPath);
580    }
581
582    /**
583     * Configures a whole tool root with all its tools.<p>
584     *
585     * @param cms the cms context
586     * @param toolRoot the tool root to configure
587     *
588     * @throws CmsException if something goes wrong
589     */
590    private void configureToolRoot(CmsObject cms, CmsToolRootHandler toolRoot) throws CmsException {
591
592        List<I_CmsToolHandler> handlers = new ArrayList<I_CmsToolHandler>();
593
594        // add tool root handler
595        handlers.add(toolRoot);
596
597        // look in every file under the root uri for valid
598        // admin tools and register them
599        List<CmsResource> resources = cms.readResourcesWithProperty(toolRoot.getUri(), HANDLERCLASS_PROPERTY);
600        Iterator<CmsResource> itRes = resources.iterator();
601        while (itRes.hasNext()) {
602            CmsResource res = itRes.next();
603            CmsProperty prop = cms.readPropertyObject(res.getRootPath(), HANDLERCLASS_PROPERTY, false);
604            if (!prop.isNullProperty()) {
605                try {
606                    // instantiate the handler
607                    Class<?> handlerClass = Class.forName(prop.getValue());
608                    I_CmsToolHandler handler = (I_CmsToolHandler)handlerClass.newInstance();
609
610                    if (!handler.setup(cms, toolRoot, res.getRootPath())) {
611                        // log failure
612                        if (CmsLog.INIT.isWarnEnabled()) {
613                            CmsLog.INIT.warn(
614                                Messages.get().getBundle().key(
615                                    Messages.INIT_TOOLMANAGER_TOOL_SETUP_ERROR_1,
616                                    res.getRootPath()));
617                        }
618                    }
619
620                    // keep for later use
621                    handlers.add(handler);
622                    // log success
623                    if (CmsLog.INIT.isDebugEnabled()) {
624                        if (!handler.getLink().equals(VIEW_JSPPAGE_LOCATION)) {
625                            CmsLog.INIT.debug(
626                                Messages.get().getBundle().key(
627                                    Messages.INIT_TOOLMANAGER_NEWTOOL_FOUND_2,
628                                    handler.getPath(),
629                                    handler.getLink()));
630                        } else {
631                            CmsLog.INIT.debug(
632                                Messages.get().getBundle().key(
633                                    Messages.INIT_TOOLMANAGER_NEWTOOL_FOUND_2,
634                                    handler.getPath(),
635                                    res.getRootPath()));
636                        }
637                    }
638                } catch (Exception e) {
639                    // log failure
640                    if (CmsLog.INIT.isWarnEnabled()) {
641                        CmsLog.INIT.warn(
642                            Messages.get().getBundle().key(
643                                Messages.INIT_TOOLMANAGER_TOOL_SETUP_ERROR_1,
644                                res.getRootPath()),
645                            e);
646                    }
647                }
648            }
649        }
650        registerHandlerList(cms, toolRoot, 1, handlers);
651    }
652
653    /**
654     * Creates a parameter map from the given url and additional parameters.<p>
655     *
656     * @param wp the workplace context
657     * @param url the url to create the parameter map for (extracting query params)
658     * @param params additional parameter map
659     *
660     * @return the new parameter map
661     */
662    private Map<String, String[]> createToolParams(CmsWorkplace wp, String url, Map<String, String[]> params) {
663
664        Map<String, String[]> newParams = new HashMap<String, String[]>();
665        // add query parameters to the parameter map if required
666        if (url.indexOf("?") > 0) {
667            String query = url.substring(url.indexOf("?"));
668            Map<String, String[]> reqParameters = CmsRequestUtil.createParameterMap(query);
669            newParams.putAll(reqParameters);
670        }
671        if (params != null) {
672            newParams.putAll(params);
673        }
674
675        // put close link if not set
676        if (!newParams.containsKey(CmsDialog.PARAM_CLOSELINK)) {
677            Map<String, String[]> argMap = resolveAdminTool(
678                getCurrentRoot(wp).getKey(),
679                getCurrentToolPath(wp)).getHandler().getParameters(wp);
680            newParams.put(
681                CmsDialog.PARAM_CLOSELINK,
682                new String[] {linkForToolPath(wp.getJsp(), getCurrentToolPath(wp), argMap)});
683        }
684        return newParams;
685    }
686
687    /**
688     * Registers a new tool at a given install point for the given tool root.<p>
689     *
690     * @param cms the cms context object
691     * @param toolRoot the tool root
692     * @param handler the handler to install
693     */
694    private void registerAdminTool(CmsObject cms, CmsToolRootHandler toolRoot, I_CmsToolHandler handler) {
695
696        String link = handler.getLink();
697        if (link.indexOf("?") > 0) {
698            link = link.substring(0, link.indexOf("?"));
699        }
700        // check visibility
701        if (!cms.existsResource(link)) {
702            return;
703        }
704
705        //validate path
706        if (!validatePath(toolRoot.getKey(), handler.getPath(), false)) {
707            // log failure
708            if (CmsLog.INIT.isWarnEnabled()) {
709                CmsLog.INIT.warn(
710                    Messages.get().getBundle().key(
711                        Messages.INIT_TOOLMANAGER_INCONSISTENT_PATH_2,
712                        handler.getPath(),
713                        handler.getLink()));
714            }
715            return;
716        }
717
718        String id = "tool" + m_tools.elementList().size();
719        CmsTool tool = new CmsTool(id, handler);
720
721        try {
722            // try to find problems in custom tools
723            handler.isEnabled(cms);
724            handler.isVisible(cms);
725        } catch (Throwable ex) {
726            String message = Messages.get().getBundle().key(
727                Messages.INIT_TOOLMANAGER_INSTALL_ERROR_2,
728                handler.getPath(),
729                handler.getLink());
730            if (CmsLog.INIT.isWarnEnabled()) {
731                CmsLog.INIT.warn(message);
732            } else if (CmsLog.INIT.isDebugEnabled()) {
733                CmsLog.INIT.debug(message, ex);
734            }
735            return;
736        }
737
738        try {
739            // try to register, can fail if path is already used by another tool
740            m_tools.addIdentifiableObject(toolRoot.getKey() + ROOT_SEPARATOR + handler.getPath(), tool);
741            // just for fast association of links with tools
742            m_urls.addIdentifiableObject(link, handler.getPath());
743        } catch (Throwable ex) {
744            CmsLog.INIT.warn(
745                Messages.get().getBundle().key(
746                    Messages.INIT_TOOLMANAGER_DUPLICATED_ERROR_3,
747                    handler.getPath(),
748                    handler.getLink(),
749                    resolveAdminTool(toolRoot.getKey(), handler.getPath()).getHandler().getLink()));
750        }
751    }
752
753    /**
754     * Registers all tool handlers recursively for a given tool root.<p>
755     *
756     * @param cms the cms context object
757     * @param toolRoot the tool root
758     * @param len the recursion level
759     * @param handlers the list of handlers to register
760     */
761    private void registerHandlerList(
762        CmsObject cms,
763        CmsToolRootHandler toolRoot,
764        int len,
765        List<I_CmsToolHandler> handlers) {
766
767        boolean found = false;
768        Iterator<I_CmsToolHandler> it = handlers.iterator();
769        while (it.hasNext()) {
770            I_CmsToolHandler handler = it.next();
771            int myLen = CmsStringUtil.splitAsArray(handler.getPath(), TOOLPATH_SEPARATOR).length;
772            if (((len == myLen) && !handler.getPath().equals(TOOLPATH_SEPARATOR))
773                || ((len == 1) && handler.getPath().equals(TOOLPATH_SEPARATOR))) {
774                found = true;
775                registerAdminTool(cms, toolRoot, handler);
776            }
777        }
778        if (found) {
779            registerHandlerList(cms, toolRoot, len + 1, handlers);
780        }
781
782    }
783
784    /**
785     * Given a string a valid and visible tool path is computed.<p>
786     *
787     * @param wp the workplace object
788     * @param path the path to repair
789     *
790     * @return a valid and visible tool path
791     */
792    private String repairPath(CmsWorkplace wp, String path) {
793
794        String rootKey = getCurrentRoot(wp).getKey();
795        // navigate until to reach a valid path
796        while (!validatePath(rootKey, path, true)) {
797            // log failure
798            LOG.warn(Messages.get().getBundle().key(Messages.LOG_MISSING_ADMIN_TOOL_1, path));
799            // try parent
800            path = getParent(wp, path);
801        }
802        // navigate until to reach a valid tool
803        while ((resolveAdminTool(rootKey, path) == null) && !"/".equals(path)) {
804            // log failure
805            LOG.warn(Messages.get().getBundle().key(Messages.LOG_MISSING_ADMIN_TOOL_1, path));
806            // try parent
807            path = getParent(wp, path);
808        }
809
810        // navigate until to reach an enabled path
811        CmsTool aTool = resolveAdminTool(rootKey, path);
812        while ((aTool != null) && !aTool.getHandler().isEnabled(wp)) {
813            if (aTool.getHandler().getLink().equals(VIEW_JSPPAGE_LOCATION)) {
814                // just grouping
815                break;
816            }
817            path = getParent(wp, path);
818            aTool = resolveAdminTool(rootKey, path);
819        }
820
821        return path;
822    }
823
824    /**
825     * Tests if the full tool path is available.<p>
826     *
827     * @param rootKey the root tool
828     * @param toolPath the path
829     * @param full if <code>true</code> the whole path is checked, if not the last part is not checked (for new tools)
830     *
831     * @return if valid or not
832     */
833    private boolean validatePath(String rootKey, String toolPath, boolean full) {
834
835        if (toolPath.equals(TOOLPATH_SEPARATOR)) {
836            return true;
837        }
838        if (!toolPath.startsWith(TOOLPATH_SEPARATOR)) {
839            return false;
840        }
841        List<String> groups = CmsStringUtil.splitAsList(toolPath, TOOLPATH_SEPARATOR);
842        Iterator<String> itGroups = groups.iterator();
843        String subpath = "";
844        while (itGroups.hasNext()) {
845            String group = itGroups.next();
846            if (subpath.length() != TOOLPATH_SEPARATOR.length()) {
847                subpath += TOOLPATH_SEPARATOR + group;
848            } else {
849                subpath += group;
850            }
851            if (itGroups.hasNext() || full) {
852                try {
853                    // just check if the tool is available
854                    resolveAdminTool(rootKey, subpath).toString();
855                } catch (Exception e) {
856                    return false;
857                }
858            }
859        }
860        return true;
861    }
862}