001/* 002 * (C) Copyright 2006-2019 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu 018 */ 019package org.nuxeo.runtime.model; 020 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.stream.Collectors; 025 026/** 027 * This is a contribution registry that is managing contribution fragments and merge them as needed. The implementation 028 * will be notified through {@link #contributionUpdated(String, Object, Object)} each time you need to store or remove a 029 * contribution. Note that contribution objects that are registered by your implementation <b>must</b> not be modified. 030 * You can see them as immutable objects - otherwise your local changes will be lost at the next update event. 031 * <p> 032 * To use it you should extends this abstract implementation and implement the abstract methods. 033 * <p> 034 * The implementation registry doesn't need to be thread safe since it will be called from synchronized methods. 035 * <p> 036 * Also, the contribution object 037 * <p> 038 * A simple implementation is: 039 * 040 * <pre> 041 * public class MyRegistry extends ContributionFragmentRegistry<MyContribution> { 042 * public Map<String, MyContribution> registry = new HAshMap<String, MyContribution>(); 043 * 044 * public String getContributionId(MyContribution contrib) { 045 * return contrib.getId(); 046 * } 047 * 048 * public void contributionUpdated(String id, MyContribution contrib, MyContribution origContrib) { 049 * registry.put(id, contrib); 050 * } 051 * 052 * public void contributionRemoved(String id, MyContribution origContrib) { 053 * registry.remove(id); 054 * } 055 * 056 * public MyContribution clone(MyContribution contrib) { 057 * MyContribution clone = new MyContribution(contrib.getId()); 058 * clone.setSomeProperty(contrib.getSomeProperty()); 059 * ... 060 * return clone; 061 * } 062 * 063 * public void merge(MyContribution src, MyContribution dst) { 064 * dst.setSomeProperty(src.getSomeProperty()); 065 * ... 066 * } 067 * } 068 * </pre> 069 * 070 * Since 5.5, if the registry does not support merging of resources, you can just override the method 071 * {@link #isSupportingMerge()} and return false, so that {@link #merge(Object, Object)} and {@link #clone()} are never 072 * called. 073 * 074 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 075 * @see SimpleContributionRegistry 076 * @deprecated since 10.3 use DefaultComponent descriptors management methods instead 077 */ 078@Deprecated(since = "10.3") 079public abstract class ContributionFragmentRegistry<T> { 080 081 protected Map<String, FragmentList<T>> contribs = new HashMap<>(); 082 083 /** 084 * Returns the contribution ID given the contribution object 085 */ 086 public abstract String getContributionId(T contrib); 087 088 /** 089 * Adds or updates a contribution. 090 * <p> 091 * If the contribution doesn't yet exists then it will be added, otherwise the value will be updated. If the given 092 * value is null the existing contribution must be removed. 093 * <p> 094 * The second parameter is the contribution that should be updated when merging, as well as stored and used. This 095 * usually represents a clone of the original contribution or a merge of multiple contribution fragments. 096 * Modifications on this object at application level will be lost on next method call on the same object id: 097 * modifications should be done in the {@link #merge(Object, Object)} method. 098 * <p> 099 * The last parameter is the new contribution object, unchanged (original) which was neither cloned nor merged. This 100 * object should never be modified at application level, because it will be used each time a subsequent merge is 101 * done. Also, it never should be stored. 102 * 103 * @param id - the id of the contribution that needs to be updated 104 * @param contrib the updated contribution object that 105 * @param newOrigContrib - the new, unchanged (original) contribution fragment that triggered the update. 106 */ 107 public abstract void contributionUpdated(String id, T contrib, T newOrigContrib); 108 109 /** 110 * All the fragments in the contribution was removed. Contribution must be unregistered. 111 * <p> 112 * The first parameter is the contribution ID that should be remove and the second parameter the original 113 * contribution fragment that as unregistered causing the contribution to be removed. 114 */ 115 public abstract void contributionRemoved(String id, T origContrib); 116 117 /** 118 * CLone the given contribution object 119 */ 120 public abstract T clone(T orig); 121 122 /** 123 * Merge 'src' into 'dst'. When merging only the 'dst' object is modified. 124 * 125 * @param src the object to copy over the 'dst' object 126 * @param dst this object is modified 127 */ 128 public abstract void merge(T src, T dst); 129 130 /** 131 * Returns true if merge is supported. 132 * <p> 133 * Hook method to be overridden if merge logics behind {@link #clone()} and {@link #merge(Object, Object)} cannot be 134 * implemented. 135 * 136 * @since 5.5 137 */ 138 public boolean isSupportingMerge() { 139 return true; 140 } 141 142 /** 143 * Add a new contribution. This will start install the new contribution and will notify the implementation about the 144 * value to add. (the final value to add may not be the same object as the one added - but a merge between multiple 145 * contributions) 146 */ 147 public synchronized void addContribution(T contrib) { 148 String id = getContributionId(contrib); 149 FragmentList<T> head = addFragment(id, contrib); 150 contributionUpdated(id, head.merge(this), contrib); 151 } 152 153 /** 154 * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it 155 * should store (after re-merging contribution fragments). 156 * <p> 157 * Uses standard equality to check for old objects (useEqualsMethod == false). 158 * 159 * @see #removeContribution(Object, boolean) 160 */ 161 public synchronized void removeContribution(T contrib) { 162 removeContribution(contrib, false); 163 } 164 165 /** 166 * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it 167 * should store (after re-merging contribution fragments). 168 * <p> 169 * Equality can be controlled from here. 170 * <p> 171 * Contributions come from the runtime that keeps exact instances, so using equality usually makes it possible to 172 * remove the exact instance that was contributed by this component (without needing to reference the component name 173 * for instance). But when unit-testing, or when registrating contributions that do not come directly from the 174 * runtime, regirties need to use the equals method defined on each contribution. 175 * 176 * @param contrib the contrib to remove 177 * @param useEqualsMethod a boolean stating that old contributions should be checked using the equals method instead 178 * of 179 * @since 5.6 180 */ 181 public synchronized void removeContribution(T contrib, boolean useEqualsMethod) { 182 String id = getContributionId(contrib); 183 FragmentList<T> head = removeFragment(id, contrib, useEqualsMethod); 184 if (head != null) { 185 T result = head.merge(this); 186 if (result != null) { 187 contributionUpdated(id, result, contrib); 188 } else { 189 contributionRemoved(id, contrib); 190 } 191 } 192 } 193 194 /** 195 * Get a merged contribution directly from the internal registry - and avoid passing by the implementation registry. 196 * Note that this operation will invoke a merge of existing fragments if needed. 197 * <p> 198 * Since 5.5, this method has made protected as it should not be used by the service retrieving merged resources 199 * (otherwise merge will be done again). If you'd really like to call it, add a public method on your registry 200 * implementation that will call it. 201 */ 202 protected synchronized T getContribution(String id) { 203 FragmentList<T> head = contribs.get(id); 204 return head != null ? head.merge(this) : null; 205 } 206 207 /** 208 * Get an array of all contribution fragments 209 */ 210 @SuppressWarnings("unchecked") 211 public synchronized FragmentList<T>[] getFragments() { 212 return contribs.values().toArray(new FragmentList[0]); 213 } 214 215 protected FragmentList<T> addFragment(String id, T contrib) { 216 FragmentList<T> head = contribs.get(id); 217 if (head == null) { 218 // no merge needed 219 head = new FragmentList<>(); 220 this.contribs.put(id, head); 221 } 222 head.add(contrib); 223 return head; 224 } 225 226 protected FragmentList<T> removeFragment(String id, T contrib, boolean useEqualsMethod) { 227 FragmentList<T> head = contribs.get(id); 228 if (head == null) { 229 return null; 230 } 231 if (head.remove(contrib, useEqualsMethod)) { 232 if (head.isEmpty()) { 233 contribs.remove(id); 234 } 235 return head; 236 } 237 return null; 238 } 239 240 /** 241 * @since 9.3 242 */ 243 public synchronized Map<String, T> toMap() { 244 return contribs.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> e.getValue().merge(this))); 245 } 246 247 public static class FragmentList<T> extends Fragment<T> { 248 249 public FragmentList() { 250 super(null); 251 prev = this; 252 next = this; 253 } 254 255 public boolean isEmpty() { 256 return next == null; 257 } 258 259 public T merge(ContributionFragmentRegistry<T> reg) { 260 T mergedValue = object; 261 if (mergedValue != null) { 262 return mergedValue; 263 } 264 Fragment<T> p = next; 265 if (p == this) { 266 return null; 267 } 268 mergedValue = reg.isSupportingMerge() ? reg.clone(p.object) : p.object; 269 p = p.next; 270 while (p != this) { 271 if (reg.isSupportingMerge()) { 272 reg.merge(p.object, mergedValue); 273 } else { 274 mergedValue = p.object; 275 } 276 p = p.next; 277 } 278 object = mergedValue; 279 return mergedValue; 280 } 281 282 public final void add(T contrib) { 283 insertBefore(new Fragment<>(contrib)); 284 object = null; 285 } 286 287 public final void add(Fragment<T> fragment) { 288 insertBefore(fragment); 289 object = null; 290 } 291 292 public boolean remove(T contrib) { 293 return remove(contrib, false); 294 } 295 296 /** 297 * @since 5.6 298 */ 299 public boolean remove(T contrib, boolean useEqualsMethod) { 300 Fragment<T> p = next; 301 while (p != this) { 302 if (useEqualsMethod && p.object != null && p.object.equals(contrib) 303 || !useEqualsMethod && p.object == contrib) { 304 p.remove(); 305 object = null; 306 return true; 307 } 308 p = p.next; 309 } 310 return false; 311 } 312 } 313 314 public static class Fragment<T> { 315 public T object; 316 317 public Fragment<T> next; 318 319 public Fragment<T> prev; 320 321 public Fragment(T object) { 322 this.object = object; 323 } 324 325 public final void insertBefore(Fragment<T> fragment) { 326 fragment.prev = prev; 327 fragment.next = this; 328 prev.next = fragment; 329 prev = fragment; 330 } 331 332 public final void insertAfter(Fragment<T> fragment) { 333 fragment.prev = this; 334 fragment.next = next; 335 next.prev = fragment; 336 next = fragment; 337 } 338 339 public final void remove() { 340 prev.next = next; 341 next.prev = prev; 342 next = prev = null; 343 } 344 345 public final boolean hasNext() { 346 return next != null; 347 } 348 349 public final boolean hasPrev() { 350 return prev != null; 351 } 352 } 353 354}