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 m_formattersById.put(uuid, formatter); 083 084 Set<String> relatedKeys = new HashSet<>(); 085 Set<CmsUUID> relatedIds = new HashSet<>(); 086 collectRelatedKeysAndIds(formatter.getAllKeys(), relatedKeys, relatedIds); 087 088 for (CmsUUID relatedId : relatedIds) { 089 m_keysById.removeAll(relatedId); 090 m_formattersById.remove(relatedId); 091 } 092 for (String relatedKey : relatedKeys) { 093 m_idsByKey.removeAll(relatedKey); 094 } 095 for (String relatedKey : relatedKeys) { 096 m_idsByKey.put(relatedKey, uuid); 097 } 098 m_keysById.putAll(uuid, relatedKeys); 099 } 100 } 101 102 /** 103 * Gets the final map of active formatters, with their formatter keys replaced by the total set of keys under which they should be available. 104 * 105 * @return the map of formatters by id 106 */ 107 public Map<CmsUUID, I_CmsFormatterBean> getFormattersWithAdditionalKeys() { 108 109 Map<CmsUUID, I_CmsFormatterBean> result = new HashMap<>(); 110 for (Map.Entry<CmsUUID, I_CmsFormatterBean> entry : m_formattersById.entrySet()) { 111 CmsUUID id = entry.getKey(); 112 Set<String> keys = m_keysById.get(id); 113 I_CmsFormatterBean formatter = entry.getValue(); 114 Optional<I_CmsFormatterBean> formatterWithKeys = formatter.withKeys(keys); 115 if (formatterWithKeys.isPresent()) { 116 result.put(id, formatterWithKeys.orElse(null)); 117 } 118 } 119 return result; 120 } 121 122 /** 123 * Removes the formatter with the given id. 124 * 125 * @param id the formatter id 126 */ 127 public void remove(CmsUUID id) { 128 129 Set<String> keys = m_keysById.removeAll(id); 130 for (String key : keys) { 131 m_idsByKey.remove(key, id); 132 } 133 m_formattersById.remove(id); 134 } 135 136 /** 137 * Removes all formatters matching the given predicate 138 * 139 * @param condition the condition to use for checking which formatters should be removed 140 */ 141 public void removeIf(Predicate<I_CmsFormatterBean> condition) { 142 143 Set<CmsUUID> toRemove = new HashSet<>(); 144 for (Map.Entry<CmsUUID, I_CmsFormatterBean> entry : m_formattersById.entrySet()) { 145 if (condition.test(entry.getValue())) { 146 toRemove.add(entry.getKey()); 147 } 148 } 149 toRemove.forEach(id -> remove(id)); 150 } 151 152 /** 153 * Collects all related keys and IDs which need to be removed or updated when adding a new formatter with a given set of keys. 154 * 155 * @param initialKeys the keys of the new formatter 156 * @param visitedKeys the set in which the related keys should be stored 157 * @param visitedIds the set in which the related IDs should be stored 158 */ 159 private void collectRelatedKeysAndIds( 160 Collection<String> initialKeys, 161 Set<String> visitedKeys, 162 Set<CmsUUID> visitedIds) { 163 164 visitedKeys.clear(); 165 visitedIds.clear(); 166 List<String> todo = new ArrayList<>(initialKeys); 167 while (todo.size() > 0) { 168 String key = todo.remove(todo.size() - 1); 169 if (visitedKeys.contains(key)) { 170 continue; 171 } 172 visitedKeys.add(key); 173 for (CmsUUID id : m_idsByKey.get(key)) { 174 visitedIds.add(id); 175 for (String relatedKey : m_keysById.get(id)) { 176 todo.add(relatedKey); 177 } 178 } 179 } 180 } 181 182}