/*
* File : $Source: /alkacon/cvs/alkacon/com.alkacon.opencms.formgenerator/src/com/alkacon/opencms/formgenerator/CmsSelectWidgetXmlcontentType.java,v $
* Date : $Date: 2010/11/04 08:40:32 $
* Version: $Revision: 1.13 $
*
* This file is part of the Alkacon OpenCms Add-On Module Package
*
* Copyright (c) 2010 Alkacon Software GmbH (http://www.alkacon.com)
*
* The Alkacon OpenCms Add-On Module Package is free software:
* you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alkacon OpenCms Add-On Module Package is distributed
* in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Alkacon OpenCms Add-On Module Package.
* If not, see http://www.gnu.org/licenses/.
*
* For further information about Alkacon Software GmbH, please see the
* company website: http://www.alkacon.com.
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org.
*/
package com.alkacon.opencms.formgenerator;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsRequestContext;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.loader.CmsLoaderException;
import org.opencms.main.CmsException;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsMacroResolver;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.I_CmsMacroResolver;
import org.opencms.widgets.CmsSelectWidget;
import org.opencms.widgets.CmsSelectWidgetOption;
import org.opencms.widgets.I_CmsWidget;
import org.opencms.widgets.I_CmsWidgetDialog;
import org.opencms.widgets.I_CmsWidgetParameter;
import org.opencms.xml.CmsXmlException;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentFactory;
import org.opencms.xml.types.I_CmsXmlContentValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.logging.Log;
/**
*
* A select widget that recursively collects all {@link org.opencms.xml.content.CmsXmlContent} resources of a given type
* (name) under a given path and creates select options that contain the xmlcontents field value specified by a name
* (xpath) as display String and the xmlcontents path (given) as the value.
* <p>
*
* The configuration String has to be of the following form: <br>
*
* <pre>
* "[folder=<vfspath>][|displayOptionMacro=<macro>][|resourcetypeName=<typename, typename,...>][|sortMacro=<macro>][|ignoreLocaleMatch=<boolean>][|propertyname=propertyvalue]*
* </pre>
*
* where
*
* <pre>
* <macro>
* </pre>
*
* is a String containing valid OpenCms macros or xpath expression in the form:
*
* <pre>
* "You are viewing: %(property.Title) "
* </pre>
*
* or
*
* <pre>
* "%(xpath.Firstname) %(xpath.Lastname), Nocakla inc."
* </pre>
*
* in which the xpath macros will be replaced with {@link org.opencms.xml.A_CmsXmlDocument#getValue(String, Locale)}
*
* <pre>
* <vfspath>
* </pre>
*
* is a valid resource path to a folder in the VFS where search is started from. You can use the macro "%(currentsite)"
* to only allow results from the current site (e.g. /sites/default/");
*
* <pre>
* <resourcetypeName>
* </pre>
*
* is a comma separated list of resource type names as defined in opencms-modules.xml,
*
* <pre>
* [|ignoreLocaleMatch=<boolean>]
* </pre>
*
* allows to turn off the matching of the editor locale to the locale property of the resource (prio 1 if property
* found) or the existance of that locale in the XML content (prio 2) and
*
*
* <pre>
* [|propertyname = propertyvalue]*
* </pre>
*
* is a arbitrary number of properties value mappings that have to exist on the resources to.
* <p>
*
* <b>This widget has to be used with the datatype <code>OpenCmsVfsFile</code> as it references files.</b>
* <p>
*
*
* <h3>Please note</h3>
* <p>
* <ul>
* <li>The widget will not offer XML contents that are in a different locale than the current page that displays it.
* <br>
* Only if the "matching" XML content has defined a language node for the locale that is set on the page for this widget
* and the xpath expression to display is not empty, the XML content will be selectable. </li>
* <li>If sortMacro is missing the values will be sorted alphabetically by their resolved display option (from the
* displayOptionMacro).</li>
* </ul>
* </p>
*
* <h3>Localization</h3>
* <p>
* Standard localized OpenCms web sites do contain every resource as a sibling in every language folder. Therefore it
* has to be prevented that this select widget shows every resource of the chosen type as duplicates (siblings) will be
* selectable. This is case a. <br>
* In case b this select widget is used to choose contents that are only in one place (shared tree) and exists only one
* time. In this case a check if the editor locale matches the locale property of the resource to allow for selection
* would fail. Therefore the localization handling works as follows:
*
* <ol>
* <li><b>Case a: Localized resources with siblings: </b><br/> The resources to allow for selection are filtered. They
* have to have the property locale set to the current XML content editor locale. This mode is detected if the resources
* to select have the property "locale" set. </li>
* <li><b>Case a: Shared resources with no siblings: </b><br/> The resources to allow for selection are not filtered
* by the locale property. This mode is detected if the resources to select have the property "locale" <b>not</b>set.
* </li>
* </ol>
* </p>
*
* @author Achim Westermann
*
* @version $Revision: 1.13 $
*
* @since 7.0.4
*
*/
public class CmsSelectWidgetXmlcontentType extends CmsSelectWidget {
/**
* A {@link CmsSelectWidgetOption} that is bundled with a corresponding resource that may be selected.
* <p>
*
* @author Achim Westermann
*
* @version $Revision: 1.13 $
*
* @since 6.1.6
*
*/
private static final class CmsResourceSelectWidgetOption extends CmsSelectWidgetOption {
/** The resource to select. */
private CmsResource m_resource;
/**
* Creates a non-default select option with the resource to select, the resource's name as the display text and
* no help text.
* <p>
*
* @param cms
* needed to remove the site root from the resource path.
*
* @param resource
* The resource of this selection.
*
*/
public CmsResourceSelectWidgetOption(CmsObject cms, CmsResource resource) {
this(cms, resource, false);
}
/**
* Creates a select option with the resource to select, the resource's name as the display text and no help text
* that is potentially the default selection (argument isDefault).
* <p>
*
* @param cms
* needed to remove the site root from the resource path.
*
* @param resource
* The resource of this selection.
*
* @param isDefault
* true, if this option is the default option (preselected.
*
*/
public CmsResourceSelectWidgetOption(CmsObject cms, CmsResource resource, boolean isDefault) {
this(cms, resource, isDefault, resource.getName());
}
/**
*
* Creates a select option with the resource to select, the given optionText as the display text and no help
* text that is potentially the default selection (argument isDefault).
* <p>
*
* @param cms
* needed to remove the site root from the resource path.
*
* @param resource
* The resource of this selection.
*
* @param isDefault
* true, if this option is the default option (preselected.
*
* @param optionText
* the text to display for this option.
*/
public CmsResourceSelectWidgetOption(CmsObject cms, CmsResource resource, boolean isDefault, String optionText) {
this(cms, resource, isDefault, optionText, null);
}
/**
* Creates a select option with the resource to select, the given optionText as the display text and the given
* help text that is potentially the default selection (argument isDefault).
* <p>
*
* @param cms
* needed to remove the site root from the resource path.
*
* @param resource
* The resource of this selection.
*
* @param isDefault
* true, if this option is the default option (preselected.
*
* @param optionText
* the text to display for this option.
*
* @param helpText
* The help text to display.
*/
public CmsResourceSelectWidgetOption(
CmsObject cms,
CmsResource resource,
boolean isDefault,
String optionText,
String helpText) {
super(cms.getRequestContext().removeSiteRoot(resource.getRootPath()), isDefault, optionText, helpText);
m_resource = resource;
}
/**
* Returns the resource that is selectable.
* <p>
*
* @return the resource that is selectable.
*/
CmsResource getResource() {
return m_resource;
}
}
/**
* Compares two <code>{@link CmsSelectWidgetXmlcontentType.CmsResourceSelectWidgetOption}</code> instances by any
* resource related value that may be accessed via a <code>{@link CmsMacroResolver}</code> (except message keys).
* <p>
*
* @author Achim Westermann
*
* @version $Revision: 1.13 $
*
* @since 6.1.6
*
*/
private final class CmsResourceSelectWidgetOptionComparator implements Comparator {
/** The {@link CmsMacroResolver} compatible macro to resolve for comparison. * */
private String m_comparatorMacro;
/** To access resource related values with the {@link CmsMacroResolver} for comparison. * */
private CmsObject m_macroCmsObjectInner;
/** The {@link CmsMacroResolver} to use for macro resolvation for comparison. * */
private CmsMacroResolver m_macroResolverInner;
/**
* Creates a comparator that will resolve the {@link CmsResource} related values with the given macro
* expression.
* <p>
*
* @param cms
* will be cloned and used for macro - resolvation.
*
* @param comparatorMacro
* the macro to use to find the resource related strings to compare.
*
* @throws CmsException
* if sth. goes wrong.
*
* @see CmsMacroResolver
*/
CmsResourceSelectWidgetOptionComparator(CmsObject cms, String comparatorMacro)
throws CmsException {
if (CmsStringUtil.isEmpty(comparatorMacro)) {
m_comparatorMacro = I_CmsMacroResolver.MACRO_DELIMITER
+ ""
+ I_CmsMacroResolver.MACRO_START
+ "opencms.filename)";
} else {
m_comparatorMacro = comparatorMacro;
}
m_macroCmsObjectInner = OpenCms.initCmsObject(cms);
m_macroCmsObjectInner.getRequestContext().setSiteRoot("/");
m_macroResolverInner = new CmsMacroResolver();
m_macroResolverInner.setCmsObject(m_macroCmsObjectInner);
}
/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object option1, Object option2) {
CmsResourceSelectWidgetOption op1 = (CmsResourceSelectWidgetOption)option1;
CmsResourceSelectWidgetOption op2 = (CmsResourceSelectWidgetOption)option2;
CmsResource resource1 = op1.getResource();
CmsResource resource2 = op2.getResource();
String sort1, sort2;
// fool the macro resolver:
CmsRequestContext requestContext = m_macroCmsObjectInner.getRequestContext();
requestContext.setUri(resource1.getRootPath());
// implant the resource name for macro "%(opencms.filename}):
m_macroResolverInner.setResourceName(resource1.getName());
sort1 = m_macroResolverInner.resolveMacros(m_comparatorMacro);
requestContext.setUri(resource2.getRootPath());
m_macroResolverInner.setResourceName(resource2.getName());
sort2 = m_macroResolverInner.resolveMacros(m_comparatorMacro);
int result = sort1.compareTo(sort2);
return result;
}
}
/**
* Configuration parameter to turn off match of editor locale with resource locale or existance of locale in XML
* content.
*/
public static final String CONFIGURATION_IGNORE_LOCALE_MATCH = "ignoreLocaleMatch";
/**
* Configuration parameter for construction of the option display value by a macro containing xpath macros for the
* xmlcontent.
*/
public static final String CONFIGURATION_OPTION_DISPLAY_MACRO = "displayOptionMacro";
/**
* Configuration parameter for choosing the macro to sort the display options by.
*/
public static final String CONFIGURATION_OPTION_SITE = "site";
/**
* Configuration parameter for choosing the macro to sort the display options by.
*/
public static final String CONFIGURATION_OPTION_SORT_MACRO = "sortMacro";
/** Configuration parameter to set the name of the resource types to accept. */
// TODO: Change the name to plural as a comma separated list is allowed as soon as you have time to check all xsds.
public static final String CONFIGURATION_RESOURCETYPENAME = "resourcetypeName";
/** Configuration parameter to set the top folder in the VFS to search for xmlcontent resources. */
public static final String CONFIGURATION_TOPFOLDER = "folder";
/** Macro key used to specify current site folder requested. */
public static final String MACROKEY_CURRENT_SITE = "currentsite";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsSelectWidgetXmlcontentType.class);
/** Only used for the macro resolver to resolve macros for the collected XML contents. */
protected CmsObject m_macroCmsObject;
/** The macro resolver to use. */
protected CmsMacroResolver m_macroResolver;
/**
* The macro to search for the display String of the options in xmlcontent files found below the folder to search
* in.
*/
private String m_displayOptionMacro;
/** A map filled with properties and their values that have to exist on values to display. */
private Map<String, String> m_filterProperties;
/**
* If true it is not tried to match the editor locale with the existence of the locale in the XML content or as
* locale property of the corresponding resource.
*/
private boolean m_ignoreLocaleMatching;
/** The resource folder under which the xmlcontent resources will be searched. */
private CmsResource m_resourceFolder;
/** The List of type id of xmlcontent resources to use. */
private List<Integer> m_resourceTypeIDs;
/**
* The macro that describes the {@link CmsResource} - related value to use for sorting of the select widget options.
*/
private String m_sortMacro;
/**
* Creates an unconfigured widget that has to be configured by
* {@link org.opencms.widgets.A_CmsWidget#setConfiguration(String)} before any html output API call is triggered.
* <p>
*
*/
public CmsSelectWidgetXmlcontentType() {
this("");
}
/**
* Creates an instance with the given configuration.
* <p>
*
* @param configuration
* see the class description for the format.
*/
public CmsSelectWidgetXmlcontentType(String configuration) {
super(configuration);
m_filterProperties = new HashMap<String, String>();
}
/**
* Returns the displayOptionXpathMacro.
* <p>
*
* @return the displayOptionXpathMacro
*/
public String getDisplayOptionMacro() {
return m_displayOptionMacro;
}
/**
* Returns the resourceFolder under which xmlcontent resources will be investigated recursively.
* <p>
*
* @return the resourceFolder
*/
public CmsResource getResourceFolder() {
return m_resourceFolder;
}
/**
* Returns the list of resource type ids (Integer).
* <p>
*
* @return the List of resource type ids
*/
public List<Integer> getResourceTypeIDs() {
return m_resourceTypeIDs;
}
/**
* Returns the ignoreLocaleMatching.
* <p>
*
* @return the ignoreLocaleMatching
*/
public boolean isIgnoreLocaleMatching() {
return m_ignoreLocaleMatching;
}
/**
* @see org.opencms.widgets.CmsSelectWidget#newInstance()
*/
@Override
public I_CmsWidget newInstance() {
return new CmsSelectWidgetXmlcontentType(getConfiguration());
}
/**
* Sets the ignoreLocaleMatching.
* <p>
*
* @param ignoreLocaleMatching
* the ignoreLocaleMatching to set
*/
public void setIgnoreLocaleMatching(boolean ignoreLocaleMatching) {
m_ignoreLocaleMatching = ignoreLocaleMatching;
}
/**
* Returns the list of configured select options, parsing the configuration String if required.
* <p>
*
* @param cms
* the current users OpenCms context.
*
* @param widgetDialog
* the dialog of this widget.
*
* @param param
* the widget parameter of this dialog.
*
* @see org.opencms.widgets.A_CmsSelectWidget#parseSelectOptions(org.opencms.file.CmsObject,
* org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter)
*
* @return the list of configured select options.
*
* @throws CmsIllegalArgumentException
* if the "folder" property of the configuration does not denote a folder within the VFS.
*/
@Override
protected List parseSelectOptions(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param)
throws CmsIllegalArgumentException {
Locale dialogContentLocale = ((I_CmsXmlContentValue)param).getLocale();
Locale resourceLocale;
if (m_macroCmsObject == null) {
try {
m_macroCmsObject = OpenCms.initCmsObject(cms);
m_macroCmsObject.getRequestContext().setSiteRoot("/");
} catch (CmsException e) {
// should never happen
if (LOG.isErrorEnabled()) {
LOG.error(Messages.get().getBundle().key(
Messages.ERR_SELECTWIDGET_INTERNAL_CONFIGURATION_2,
new Object[] {getClass().getName(), getConfiguration()}));
}
return Collections.EMPTY_LIST;
}
}
if (m_macroResolver == null) {
m_macroResolver = new CmsMacroResolver();
m_macroResolver.setCmsObject(m_macroCmsObject);
m_macroResolver.setKeepEmptyMacros(true);
}
List selectOptions = getSelectOptions();
if (selectOptions == null) {
String configuration = getConfiguration();
if (configuration == null) {
// workaround: use the default value to parse the options
configuration = param.getDefault(cms);
}
try {
// parse configuration to members
parseConfigurationInternal(configuration, cms, param);
// build the set of sorted options
List<CmsResourceSelectWidgetOption> sortOptions = new ArrayList<CmsResourceSelectWidgetOption>();
CmsResourceSelectWidgetOption option;
List<CmsResource> resources;
List<CmsResource> allResources = new LinkedList<CmsResource>();
// collect all subresources of resource folder.
// As a CmsResourceFilter is somewhat limited we have to do several reads
// for each resourceType we allow:
int resType;
Iterator<Integer> itResTypes = this.m_resourceTypeIDs.iterator();
while (itResTypes.hasNext()) {
resType = (itResTypes.next()).intValue();
CmsResourceFilter filter = CmsResourceFilter.ALL.addRequireType(resType);
CmsRequestContext context = cms.getRequestContext();
String oldSiteroot = context.getSiteRoot();
context.setSiteRoot("/");
resources = cms.readResources(m_resourceFolder.getRootPath(), filter, true);
context.setSiteRoot(oldSiteroot);
if (resources.size() == 0) {
if (LOG.isErrorEnabled()) {
LOG.error(Messages.get().getBundle().key(
Messages.LOG_ERR_SELECTWIDGET_NO_RESOURCES_FOUND_3,
configuration,
m_resourceFolder.getRootPath(),
OpenCms.getResourceManager().getResourceType(resType).getTypeName()));
}
} else {
allResources.addAll(resources);
}
}
Iterator<CmsResource> itResources = allResources.iterator();
CmsResource resource;
String displayName;
// inner loop vars :
while (itResources.hasNext()) {
resource = itResources.next();
// don't make resources selectable that have a different locale than the
// we read the locale node of the xmlcontent instance matching the resources
// locale property (or top level locale).
CmsProperty resourceLocaleProperty = cms.readPropertyObject(
resource,
CmsPropertyDefinition.PROPERTY_LOCALE,
true);
resourceLocale = OpenCms.getLocaleManager().getDefaultLocale(cms, cms.getSitePath(resource));
// We allow all resources without locale property and only the
// resources with locale property that match the current XML content editor locale.
if (isIgnoreLocaleMatching()
|| ((resourceLocaleProperty.isNullProperty() && containsLocale(
cms,
resource,
dialogContentLocale)) || dialogContentLocale.equals(resourceLocale))) {
// macro resolvation within hasFilterProperty will resolve values to the
// current request
if (hasFilterProperty(resource, cms)) {
// implant the uri to the special cms object for resolving macros from
// the collected xml contents:
m_macroCmsObject.getRequestContext().setUri(resource.getRootPath());
// implant the resource for macro "%(opencms.filename)"
m_macroResolver.setResourceName(resource.getName());
// implant the messages
m_macroResolver.setMessages(widgetDialog.getMessages());
// filter out unwanted resources - if no filter properties are defined,
// every
// resource collected here is ok:
displayName = m_macroResolver.resolveMacros(getDisplayOptionMacro());
// deal with a bug of the macro resolver: it will return "" if it gets
// "%(unknown.thin)":
if (CmsStringUtil.isEmptyOrWhitespaceOnly(displayName)) {
// it was a "%(xpath.field})" expression only and swallowed by macro
// resolver:
displayName = resolveXpathMacros(cms, resource, getDisplayOptionMacro());
} else {
// there was more than one xpath macro: allow further replacements
// within partly resolved macro:
displayName = resolveXpathMacros(cms, resource, displayName);
}
// final check:
if (CmsStringUtil.isEmpty(displayName)) {
displayName = resource.getName();
}
displayName = resolveXpathMacros(cms, resource, displayName);
if (!CmsStringUtil.isEmpty(displayName)) {
// now everything required is there:
option = new CmsResourceSelectWidgetOption(cms, resource, false, displayName);
sortOptions.add(option);
}
}
}
}
selectOptions = new LinkedList<CmsResourceSelectWidgetOption>(sortOptions);
// sort the found resources according to their file name (without path information)
Collections.sort(selectOptions, new CmsResourceSelectWidgetOptionComparator(
m_macroCmsObject,
m_sortMacro));
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(Messages.get().getBundle().key(
Messages.ERR_SELECTWIDGET_CONFIGURATION_2,
getClass(),
configuration), e);
}
}
if ((selectOptions == null) || (selectOptions == Collections.EMPTY_LIST)) {
selectOptions = new ArrayList<CmsResourceSelectWidgetOption>();
}
// no method to add the parsed option list....
// Caution: if it is decided to return a copy of the list we are doomed unless
// setSelectOptions is set to protected!
List pOptions = getSelectOptions();
if (pOptions != null) {
pOptions.clear();
}
Iterator<CmsResourceSelectWidgetOption> it = selectOptions.iterator();
while (it.hasNext()) {
addSelectOption(it.next());
}
}
return selectOptions;
}
/**
* Checks if the given XML content resource contains the given locale.
*
* @param cms
* needed to add
*
* @param resource
* the XML content resource to check
*
* @param dialogContentLocale
* the locale to search for
*
* @return true if the XML content specified by the resource parameter contains the given resource or false if not
* or anything happens (the resource is no xml content,...)
*/
private boolean containsLocale(CmsObject cms, CmsResource resource, Locale dialogContentLocale) {
boolean result = false;
try {
CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(cms, cms.readFile(resource));
result = xmlcontent.getLocales().contains(dialogContentLocale);
} catch (CmsXmlException e) {
// nop
} catch (CmsException e) {
// nop
}
return result;
}
private boolean hasFilterProperty(CmsResource resource, CmsObject cms) throws CmsException {
boolean result = false;
Iterator<Map.Entry<String, String>> itFilterProperties;
Map.Entry<String, String> entry;
CmsProperty property;
// filter out unwanted resources - if no filter properties are defined, every
// resource collected here is ok:
if (m_filterProperties.size() > 0) {
itFilterProperties = m_filterProperties.entrySet().iterator();
while (itFilterProperties.hasNext()) {
entry = itFilterProperties.next();
property = cms.readPropertyObject(resource, entry.getKey(), true);
if (property == CmsProperty.getNullProperty()) {
continue;
} else {
// check if value is ok:
if (property.getValue().equals(entry.getValue())) {
// Ok, resource granted:
result = true;
break;
} else {
// Failed, try further filter properties for match:
}
}
}
} else {
// don't filter if now filter props configured
result = true;
}
return result;
}
/**
* Parses the configuration and puts it to the member variables.
* <p>
*
* Only invoked if options were not parsed before in this instance.
* <p>
*
* @param configuration
* the configuration (with resolved macros).
*
* @param cms
* needed to read the resource folder to use.
*
* @param param
* allows to access the resource currently being rendered.
*
*
* @throws CmsIllegalArgumentException
* if the configuration is invalid.
*
*/
private void parseConfigurationInternal(String configuration, CmsObject cms, I_CmsWidgetParameter param) {
/*
* prepare for macro resolvation of property value against the resource currently rendered
* implant the uri to the special cms object for resolving macros from the collected xml contents:
*/CmsFile file = ((I_CmsXmlContentValue)param).getDocument().getFile();
m_macroCmsObject.getRequestContext().setUri(file.getRootPath());
List<String> mappings = CmsStringUtil.splitAsList(configuration, '|');
Iterator<String> itMappings = mappings.iterator();
String mapping;
String[] keyValue;
String key;
String value;
boolean displayMacroFound = false, sortMacroFound = false, folderFound = false, typeFound = false;
// LOG.info("Setting macro %(currentsite) to: " + cms.getRequestContext().getSiteRoot());
m_macroResolver.addMacro(MACROKEY_CURRENT_SITE, cms.getRequestContext().getSiteRoot());
while (itMappings.hasNext()) {
mapping = itMappings.next();
keyValue = CmsStringUtil.splitAsArray(mapping, '=');
if (keyValue.length != 2) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEYVALUE_LENGTH_1,
mapping));
}
key = keyValue[0].trim();
value = keyValue[1].trim();
// implant the resource for macro "%(opencms.filename)"
m_macroResolver.setResourceName(file.getName());
// check key
if (CONFIGURATION_OPTION_DISPLAY_MACRO.equals(key)) {
if (displayMacroFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_DUPLICATE_2,
key,
configuration));
}
m_displayOptionMacro = value;
displayMacroFound = true;
} else if (CONFIGURATION_OPTION_SORT_MACRO.equals(key)) {
if (sortMacroFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_DUPLICATE_2,
key,
configuration));
}
m_sortMacro = value;
sortMacroFound = true;
} else if (CONFIGURATION_RESOURCETYPENAME.equals(key)) {
if (typeFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_DUPLICATE_2,
key,
configuration));
}
// check if resource type name is OK
// if setResourceType will be implemented copy here and invoke that one
String resType = "n/a";
try {
this.m_resourceTypeIDs = new LinkedList<Integer>();
List<String> types = CmsStringUtil.splitAsList(value, ',');
Iterator<String> itTypes = types.iterator();
while (itTypes.hasNext()) {
resType = itTypes.next();
this.m_resourceTypeIDs.add(new Integer(
OpenCms.getResourceManager().getResourceType(resType).getTypeId()));
}
} catch (CmsLoaderException e) {
throw new CmsIllegalArgumentException(org.opencms.file.Messages.get().container(
org.opencms.file.Messages.ERR_UNKNOWN_RESOURCE_TYPE_1,
resType), e);
}
typeFound = true;
} else if (CONFIGURATION_TOPFOLDER.equals(key)) {
if (folderFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_DUPLICATE_2,
key,
configuration));
}
// allow collector path to contain macros relative to the current resource:
value = m_macroResolver.resolveMacros(value);
try {
CmsRequestContext context = cms.getRequestContext();
String oldSiteRoot = context.getSiteRoot();
context.setSiteRoot("/");
CmsResource resource = cms.readResource(value);
context.setSiteRoot(oldSiteRoot);
if (resource.isFile()) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_RESOURCE_NOFOLDER_2,
value,
configuration));
}
m_resourceFolder = resource;
} catch (CmsException e) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_RESOURCE_INVALID_2,
value,
configuration), e);
}
folderFound = true;
} else if (CONFIGURATION_IGNORE_LOCALE_MATCH.equals(key)) {
m_ignoreLocaleMatching = Boolean.valueOf(value).booleanValue();
} else {
// a property=value definition???
CmsPropertyDefinition propDef;
try {
propDef = cms.readPropertyDefinition(key);
} catch (CmsException e) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_UNKNOWN_2,
key,
getClass().getName()), e);
}
if (propDef != null) {
// a valid property - value combination to filter resources for:
// value is potentially a macro that will be compared to the current xml content
// resource!
value = m_macroResolver.resolveMacros(value);
m_filterProperties.put(key, value);
} else {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_UNKNOWN_2,
key,
getClass().getName()));
}
}
}
// final check wether all has been set
if (!displayMacroFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_MISSING_3,
CONFIGURATION_OPTION_DISPLAY_MACRO,
configuration,
getClass().getName()));
}
if (!folderFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_MISSING_3,
CONFIGURATION_TOPFOLDER,
configuration,
getClass().getName()));
}
if (!typeFound) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.ERR_SELECTWIDGET_CONFIGURATION_KEY_MISSING_3,
CONFIGURATION_RESOURCETYPENAME,
configuration,
getClass().getName()));
}
}
/**
* Resolves xpath macros of the form <code>"%(xpath.XPATHEXPRESSION)"</code> by the field value of the XML content
* denoted by the given resource. <p>
*
* File loading and unmarshalling is only done if the given String contains xpath macros.
* <p>
*
* @param cms to access values in the cmsobject.
*
* @param resource the resource pointing to an xmlcontent containing the macro values to resolve.
*
* @param value the unresolved macro string.
*
* @return a String with resolved xpath macros that have been read from the xmlcontent.
*
* @throws CmsException if something goes wrong
*/
private String resolveXpathMacros(CmsObject cms, CmsResource resource, final String value) throws CmsException {
String work = value;
StringBuffer result = new StringBuffer();
String startMacro = new StringBuffer(I_CmsMacroResolver.MACRO_DELIMITER + "").append(
I_CmsMacroResolver.MACRO_START).append("xpath.").toString();
int startmacroIndex = work.indexOf(startMacro);
int stopmacro = 0;
String xpath;
if (startmacroIndex != -1) {
// for the option value we have to unmarshal...
CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(cms, cms.readFile(resource));
// we read the locale node of the xmlcontent instance matching the resources
// locale property (or top level locale).
Locale locale = CmsLocaleManager.getLocale(cms.readPropertyObject(
xmlcontent.getFile(),
CmsPropertyDefinition.PROPERTY_LOCALE,
true).getValue());
while (startmacroIndex != -1) {
stopmacro = work.indexOf(I_CmsMacroResolver.MACRO_END);
if (stopmacro == 0) {
// TODO: complain about missing closing macro bracket!
}
// first cut the prefix of the macro to put it to the result:
result.append(work.substring(0, startmacroIndex));
// now replace the macro:
xpath = work.substring(startmacroIndex + 8, stopmacro);
try {
result.append(xmlcontent.getValue(xpath, locale).getPlainText(cms));
} catch (Exception ex) {
if (LOG.isErrorEnabled()) {
LOG.error(Messages.get().getBundle().key(
Messages.LOG_ERR_SELECTWIDGET_XPATH_INVALID_4,
new Object[] {
xpath,
locale.toString(),
xmlcontent.getFile().getRootPath(),
ex.getLocalizedMessage()}));
}
}
// skip over the consumed String of value:
work = work.substring(stopmacro + 1);
// take a new start for macro:
startmacroIndex = work.indexOf(startMacro);
}
}
// append trailing value
result.append(work);
return result.toString();
}
}