001/*
002 * (C) Copyright 2010-2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Olivier Grisel
016 */
017package org.nuxeo.ecm.platform.suggestbox.service.descriptors;
018
019import java.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.nuxeo.common.xmap.annotation.XNode;
026import org.nuxeo.common.xmap.annotation.XNodeList;
027import org.nuxeo.common.xmap.annotation.XObject;
028import org.nuxeo.ecm.platform.suggestbox.service.ComponentInitializationException;
029
030@XObject("suggesterGroup")
031public class SuggesterGroupDescriptor implements Cloneable {
032
033    private static final Log log = LogFactory.getLog(SuggesterGroupDescriptor.class);
034
035    @XNode("@name")
036    protected String name = "default";
037
038    @XNodeList(value = "suggesters/suggesterName", type = ArrayList.class, componentType = SuggesterGroupItemDescriptor.class)
039    List<SuggesterGroupItemDescriptor> suggesters;
040
041    public String getName() {
042        return name;
043    }
044
045    public List<SuggesterGroupItemDescriptor> getSuggesters() {
046        return suggesters;
047    }
048
049    public void mergeFrom(SuggesterGroupDescriptor newDescriptor) throws ComponentInitializationException {
050        if (name == null || !name.equals(newDescriptor.name)) {
051            throw new RuntimeException("Cannot merge descriptor with name '" + name
052                    + "' with another descriptor with different name " + newDescriptor.getName() + "'");
053        }
054        log.info(String.format("Merging suggester group '%s'.", name));
055        // merge the suggesterNames
056        for (SuggesterGroupItemDescriptor newSuggesterGroupItem : newDescriptor.getSuggesters()) {
057            String newSuggesterName = newSuggesterGroupItem.getName();
058            // manage remove
059            if (newSuggesterGroupItem.isRemove()) {
060                boolean isSuggesterRemoved = remove(newSuggesterName);
061                if (!isSuggesterRemoved) {
062                    log.warn(String.format(
063                            "Cannot remove suggester '%s' because it does not exist in suggesterGroup '%s'.",
064                            newSuggesterName, name));
065                }
066            }
067            // manage appendBefore, appendAfter or no particular attributes
068            else {
069                String appendBeforeSuggesterName = newSuggesterGroupItem.getAppendBefore();
070                String appendAfterSuggesterName = newSuggesterGroupItem.getAppendAfter();
071                // can't have both appendBefore and appendAfter
072                if (appendBeforeSuggesterName != null && appendAfterSuggesterName != null) {
073                    throw new RuntimeException(String.format(
074                            "Cannot define both 'appendBefore' and 'appendAfter' attributes on suggester '%s'.",
075                            newSuggesterName));
076                }
077                // manage appendBefore
078                if (appendBeforeSuggesterName != null) {
079                    boolean isSuggesterAppended = appendBefore(appendBeforeSuggesterName, newSuggesterName);
080                    if (!isSuggesterAppended) {
081                        logExistingSuggesterName(newSuggesterName);
082                    }
083                }
084                // manage appendAfter
085                else if (appendAfterSuggesterName != null) {
086                    boolean isSuggesterAppended = appendAfter(appendAfterSuggesterName, newSuggesterName);
087                    if (!isSuggesterAppended) {
088                        logExistingSuggesterName(newSuggesterName);
089                    }
090                }
091                // manage the case of no particular attributes => append
092                // suggester at the end of the list
093                else if (appendBeforeSuggesterName == null && appendAfterSuggesterName == null) {
094                    boolean isSuggesterAppended = appendAfter(null, newSuggesterName);
095                    if (!isSuggesterAppended) {
096                        logExistingSuggesterName(newSuggesterName);
097                    }
098                }
099            }
100        }
101    }
102
103    /*
104     * Override the Object.clone to make it public
105     */
106    @Override
107    public Object clone() throws CloneNotSupportedException {
108        return super.clone();
109    }
110
111    /**
112     * Removes the suggester named {@code suggesterName} from the {@code #suggesters} list.
113     *
114     * @param suggesterName the suggester name
115     * @return true, if a suggester was removed
116     */
117    protected boolean remove(String suggesterName) {
118        Iterator<SuggesterGroupItemDescriptor> suggestersIt = suggesters.iterator();
119        while (suggestersIt.hasNext()) {
120            SuggesterGroupItemDescriptor suggesterGroupItem = suggestersIt.next();
121            if (suggesterName.equals(suggesterGroupItem.getName())) {
122                suggestersIt.remove();
123                log.debug(String.format("Removed suggester '%s' from suggesterGroup '%s'.", suggesterName, name));
124                return true;
125            }
126        }
127        return false;
128    }
129
130    /**
131     * Returns the index of the first occurrence of the element named {@code suggesterName} in the {@code #suggesters}
132     * list, or -1 if {@code suggesterName} is null or if this list does not contain the element.
133     *
134     * @param suggesterName the suggester name
135     * @return the index of the first occurrence of the element named {@code suggesterName} in the {@code #suggesters}
136     *         list, or -1 if {@code suggesterName} is null or if this list does not contain the element
137     */
138    protected int indexOf(String suggesterName) {
139        if (suggesterName != null) {
140            int index = 0;
141            Iterator<SuggesterGroupItemDescriptor> suggestersIt = suggesters.iterator();
142            while (suggestersIt.hasNext()) {
143                SuggesterGroupItemDescriptor suggesterGroupItem = suggestersIt.next();
144                if (suggesterName.equals(suggesterGroupItem.getName())) {
145                    return index;
146                }
147                index++;
148            }
149        }
150        return -1;
151    }
152
153    /**
154     * Unless a suggester named {@code newSuggesterName} already exists in the {@code #suggesters} list, appends a new
155     * {@code SuggesterGroupItemDescriptor} named {@code newSuggesterName} just before the suggester named
156     * {@code suggesterName} in the {@code #suggesters} list. If the suggester named {@code suggesterName} does not
157     * exist, appends the new suggester at the beginning of the list.
158     *
159     * @param suggesterName the suggester name
160     * @param newSuggesterName the name of the suggester to append
161     * @return true, if the suggester named {@code newSuggesterName} was appended to the {@code #suggesters} list
162     */
163    protected boolean appendBefore(String suggesterName, String newSuggesterName) {
164        return append(suggesterName, newSuggesterName, true);
165    }
166
167    /**
168     * Unless a suggester named {@code newSuggesterName} already exists in the {@code #suggesters} list, appends a new
169     * {@code SuggesterGroupItemDescriptor} named {@code newSuggesterName} just after the suggester named
170     * {@code suggesterName} in the {@code #suggesters} list. If the suggester named {@code suggesterName} does not
171     * exist, appends the new suggester at the end of the list.
172     *
173     * @param suggesterName the suggester name
174     * @param newSuggesterName the name of the suggester to append
175     * @return true, if the suggester named {@code newSuggesterName} was appended to the {@code #suggesters} list
176     */
177    protected boolean appendAfter(String suggesterName, String newSuggesterName) {
178        return append(suggesterName, newSuggesterName, false);
179    }
180
181    /**
182     * Unless a suggester named {@code newSuggesterName} already exists in the {@code #suggesters} list, appends a new
183     * {@code SuggesterGroupItemDescriptor} named {@code newSuggesterName} just before (if {@code before} is true) or
184     * after the suggester named {@code suggesterName} in the {@code #suggesters} list. If the suggester named
185     * {@code suggesterName} does not exist, appends the new suggester at the beginning or the end of the list,
186     * depending on {@code before}.
187     *
188     * @param suggesterName the suggester name
189     * @param newSuggesterName the name of the suggester to append
190     * @return true, if the suggester named {@code newSuggesterName} was appended to the {@code #suggesters} list
191     */
192    protected boolean append(String suggesterName, String newSuggesterName, boolean before) {
193        // check if the new suggester's name doesn't already exist in the
194        // suggesters list
195        if (indexOf(newSuggesterName) > -1) {
196            return false;
197        }
198        // new suggester
199        SuggesterGroupItemDescriptor newSuggester = new SuggesterGroupItemDescriptor(newSuggesterName);
200        int indexOfSuggester = indexOf(suggesterName);
201        if (indexOfSuggester > -1) {
202            // suggester found, append new suggester before or after it
203            int indexOfNewSuggester = before ? indexOfSuggester : indexOfSuggester + 1;
204            suggesters.add(indexOfNewSuggester, newSuggester);
205            log.debug(String.format("Appended suggester '%s' %s suggester '%s' in suggesterGroup '%s'.",
206                    newSuggesterName, before ? "before" : "after", suggesterName, name));
207        } else {
208            // suggester not found, append new suggester at the beginning or the
209            // end of the suggesters list
210            if (before) {
211                suggesters.add(0, newSuggester);
212                if (suggesterName != null) {
213                    log.warn(String.format(
214                            "Could not append suggester '%s' before suggester '%s' in suggesterGroup '%s' because '%s' does not exist in this suggesterGroup. Appended it before all suggesters.",
215                            newSuggesterName, suggesterName, name, suggesterName));
216                }
217            } else {
218                suggesters.add(newSuggester);
219                if (suggesterName != null) {
220                    log.warn(String.format(
221                            "Could not append suggester '%s' after suggester '%s' in suggesterGroup '%s' because '%s' does not exist in this suggesterGroup. Appended it after all suggesters.",
222                            newSuggesterName, suggesterName, name, suggesterName));
223                }
224            }
225        }
226        return true;
227    }
228
229    /**
230     * Logs that the suggester named {@code newSuggesterName} already exists in the {@code #suggesters} list and
231     * therefore won't be appended to it.
232     *
233     * @param newSuggesterName the new suggester name
234     */
235    protected void logExistingSuggesterName(String newSuggesterName) {
236        log.warn(String.format(
237                "Suggester '%s' already exists in suggesterGroup '%s'. Cannot have two occurrences of the same suggester, so won't append it.",
238                newSuggesterName, name));
239    }
240}