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.ade.configuration.formatters; 029 030import org.opencms.util.CmsUUID; 031import org.opencms.xml.containerpage.I_CmsFormatterBean; 032 033import java.util.ArrayList; 034import java.util.Collection; 035import java.util.HashMap; 036import java.util.HashSet; 037import java.util.List; 038import java.util.Map; 039import java.util.Optional; 040import java.util.Set; 041import java.util.function.Predicate; 042 043import com.google.common.collect.HashMultimap; 044 045/** 046 * Helper class for keeping track of which keys map to which formatters, and which formatters are active, 047 * when evaluating the sitemap configuration. 048 * 049 * <p>Formatters can now have multiple keys, which makes overriding them in more specific sitemap/master configurations more complex. 050 * Let X and Y be active sitemap/master configurations for the currently requested page, with Y being more specific than X. If X adds a formatter F1 051 * with keys A, B, C and Y adds a different formatter F2 with an overlapping set of keys C, D, E, then as a result, formatter F1 052 * should be disabled and formatter F2 should be used for the keys A, B, C, D, E (even though it does not have A and B 053 * as configured keys). 054 * 055 * <p>You use an instance of this class by adding/removing formatters in the order these operations are defined by the sitemap configuration 056 * and finally calling the getFormattersWithAdditionalKeys() method at the end, which augments the formatters it returns by adding the appropriate keys. 057 */ 058public class CmsFormatterIndex { 059 060 /** Table which maps ids to formatter keys. */ 061 private HashMultimap<CmsUUID, String> m_keysById = HashMultimap.create(); 062 063 /** Table which maps formatter keys to ids. */ 064 private HashMultimap<String, CmsUUID> m_idsByKey = HashMultimap.create(); 065 066 /** Map of formatters by id. */ 067 private Map<CmsUUID, I_CmsFormatterBean> m_formattersById = new HashMap<>(); 068 069 /** 070 * Adds the given formatter. 071 * 072 * <p>If there are any direct or indirect overlaps with the keys of already added formatters, these 073 * formatters will be removed and their keys mapped to the new formatter. 074 * 075 * @param formatter the formatter to add 076 */ 077 public void addFormatter(I_CmsFormatterBean formatter) { 078 079 String id = formatter.getId(); 080 if (CmsUUID.isValidUUID(id)) { 081 CmsUUID uuid = new CmsUUID(id); 082 083 Set<String> relatedKeys = new HashSet<>(); 084 Set<CmsUUID> relatedIds = new HashSet<>(); 085 collectRelatedKeysAndIds(formatter.getAllKeys(), relatedKeys, relatedIds); 086 087 for (CmsUUID relatedId : relatedIds) { 088 m_keysById.removeAll(relatedId); 089 m_formattersById.remove(relatedId); 090 } 091 for (String relatedKey : relatedKeys) { 092 m_idsByKey.removeAll(relatedKey); 093 } 094 095 m_formattersById.put(uuid, formatter); 096 for (String relatedKey : relatedKeys) { 097 m_idsByKey.put(relatedKey, uuid); 098 } 099 m_keysById.putAll(uuid, relatedKeys); 100 } 101 } 102 103 /** 104 * Gets the final map of active formatters, with their formatter keys replaced by the total set of keys under which they should be available. 105 * 106 * @return the map of formatters by id 107 */ 108 public Map<CmsUUID, I_CmsFormatterBean> getFormattersWithAdditionalKeys() { 109 110 Map<CmsUUID, I_CmsFormatterBean> result = new HashMap<>(); 111 for (Map.Entry<CmsUUID, I_CmsFormatterBean> entry : m_formattersById.entrySet()) { 112 CmsUUID id = entry.getKey(); 113 Set<String> keys = m_keysById.get(id); 114 I_CmsFormatterBean formatter = entry.getValue(); 115 Optional<I_CmsFormatterBean> formatterWithKeys = formatter.withKeys(keys); 116 if (formatterWithKeys.isPresent()) { 117 result.put(id, formatterWithKeys.orElse(null)); 118 } 119 } 120 return result; 121 } 122 123 /** 124 * Removes the formatter with the given id. 125 * 126 * @param id the formatter id 127 */ 128 public void remove(CmsUUID id) { 129 130 Set<String> keys = m_keysById.removeAll(id); 131 for (String key : keys) { 132 m_idsByKey.remove(key, id); 133 } 134 m_formattersById.remove(id); 135 } 136 137 /** 138 * Removes all formatters matching the given predicate 139 * 140 * @param condition the condition to use for checking which formatters should be removed 141 */ 142 public void removeIf(Predicate<I_CmsFormatterBean> condition) { 143 144 Set<CmsUUID> toRemove = new HashSet<>(); 145 for (Map.Entry<CmsUUID, I_CmsFormatterBean> entry : m_formattersById.entrySet()) { 146 if (condition.test(entry.getValue())) { 147 toRemove.add(entry.getKey()); 148 } 149 } 150 toRemove.forEach(id -> remove(id)); 151 } 152 153 /** 154 * Collects all related keys and IDs which need to be removed or updated when adding a new formatter with a given set of keys. 155 * 156 * @param initialKeys the keys of the new formatter 157 * @param visitedKeys the set in which the related keys should be stored 158 * @param visitedIds the set in which the related IDs should be stored 159 */ 160 private void collectRelatedKeysAndIds( 161 Collection<String> initialKeys, 162 Set<String> visitedKeys, 163 Set<CmsUUID> visitedIds) { 164 165 visitedKeys.clear(); 166 visitedIds.clear(); 167 List<String> todo = new ArrayList<>(initialKeys); 168 while (todo.size() > 0) { 169 String key = todo.remove(todo.size() - 1); 170 if (visitedKeys.contains(key)) { 171 continue; 172 } 173 visitedKeys.add(key); 174 for (CmsUUID id : m_idsByKey.get(key)) { 175 visitedIds.add(id); 176 for (String relatedKey : m_keysById.get(id)) { 177 todo.add(relatedKey); 178 } 179 } 180 } 181 } 182 183}