001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (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)} 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:[email protected]">Bogdan Stefanescu</a> 075 * @see SimpleContributionRegistry<T> 076 * @deprecated since 10.3 use DefaultComponent descriptors management methods instead 077 */ 078@Deprecated 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 * @param contrib 087 * @return 088 */ 089 public abstract String getContributionId(T contrib); 090 091 /** 092 * Adds or updates a contribution. 093 * <p> 094 * If the contribution doesn't yet exists then it will be added, otherwise the value will be updated. If the given 095 * value is null the existing contribution must be removed. 096 * <p> 097 * The second parameter is the contribution that should be updated when merging, as well as stored and used. This 098 * usually represents a clone of the original contribution or a merge of multiple contribution fragments. 099 * Modifications on this object at application level will be lost on next 100 * {@link #contributionUpdated(String, Object, Object)} call on the same object id: modifications should be done in 101 * the {@link #merge(Object, Object)} method. 102 * <p> 103 * The last parameter is the new contribution object, unchanged (original) which was neither cloned nor merged. This 104 * object should never be modified at application level, because it will be used each time a subsequent merge is 105 * done. Also, it never should be stored. 106 * 107 * @param id - the id of the contribution that needs to be updated 108 * @param contrib the updated contribution object that 109 * @param newOrigContrib - the new, unchanged (original) contribution fragment that triggered the update. 110 */ 111 public abstract void contributionUpdated(String id, T contrib, T newOrigContrib); 112 113 /** 114 * All the fragments in the contribution was removed. Contribution must be unregistered. 115 * <p> 116 * The first parameter is the contribution ID that should be remove and the second parameter the original 117 * contribution fragment that as unregistered causing the contribution to be removed. 118 * 119 * @param id 120 * @param origContrib 121 */ 122 public abstract void contributionRemoved(String id, T origContrib); 123 124 /** 125 * CLone the given contribution object 126 * 127 * @param object 128 * @return 129 */ 130 public abstract T clone(T orig); 131 132 /** 133 * Merge 'src' into 'dst'. When merging only the 'dst' object is modified. 134 * 135 * @param src the object to copy over the 'dst' object 136 * @param dst this object is modified 137 */ 138 public abstract void merge(T src, T dst); 139 140 /** 141 * Returns true if merge is supported. 142 * <p> 143 * Hook method to be overridden if merge logics behind {@link #clone()} and {@link #merge(Object, Object)} cannot be 144 * implemented. 145 * 146 * @since 5.5 147 */ 148 public boolean isSupportingMerge() { 149 return true; 150 } 151 152 /** 153 * Add a new contribution. This will start install the new contribution and will notify the implementation about the 154 * value to add. (the final value to add may not be the same object as the one added - but a merge between multiple 155 * contributions) 156 * 157 * @param contrib 158 */ 159 public synchronized void addContribution(T contrib) { 160 String id = getContributionId(contrib); 161 FragmentList<T> head = addFragment(id, contrib); 162 contributionUpdated(id, head.merge(this), contrib); 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 * Uses standard equality to check for old objects (useEqualsMethod == false). 170 * 171 * @param contrib 172 * @see #removeContribution(Object, boolean) 173 */ 174 public synchronized void removeContribution(T contrib) { 175 removeContribution(contrib, false); 176 } 177 178 /** 179 * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it 180 * should store (after re-merging contribution fragments). 181 * <p> 182 * Equality can be controlled from here. 183 * <p> 184 * Contributions come from the runtime that keeps exact instances, so using equality usually makes it possible to 185 * remove the exact instance that was contributed by this component (without needing to reference the component name 186 * for instance). But when unit-testing, or when registrating contributions that do not come directly from the 187 * runtime, regirties need to use the equals method defined on each contribution. 188 * 189 * @param contrib the contrib to remove 190 * @param useEqualsMethod a boolean stating that old contributions should be checked using the equals method instead 191 * of 192 * @since 5.6 193 */ 194 public synchronized void removeContribution(T contrib, boolean useEqualsMethod) { 195 String id = getContributionId(contrib); 196 FragmentList<T> head = removeFragment(id, contrib, useEqualsMethod); 197 if (head != null) { 198 T result = head.merge(this); 199 if (result != null) { 200 contributionUpdated(id, result, contrib); 201 } else { 202 contributionRemoved(id, contrib); 203 } 204 } 205 } 206 207 /** 208 * Get a merged contribution directly from the internal registry - and avoid passing by the implementation registry. 209 * Note that this operation will invoke a merge of existing fragments if needed. 210 * <p> 211 * Since 5.5, this method has made protected as it should not be used by the service retrieving merged resources 212 * (otherwise merge will be done again). If you'd really like to call it, add a public method on your registry 213 * implementation that will call it. 214 * 215 * @param id 216 * @return 217 */ 218 protected synchronized T getContribution(String id) { 219 FragmentList<T> head = contribs.get(id); 220 return head != null ? head.merge(this) : null; 221 } 222 223 /** 224 * Get an array of all contribution fragments 225 * 226 * @return 227 */ 228 @SuppressWarnings("unchecked") 229 public synchronized FragmentList<T>[] getFragments() { 230 return contribs.values().toArray(new FragmentList[contribs.size()]); 231 } 232 233 protected FragmentList<T> addFragment(String id, T contrib) { 234 FragmentList<T> head = contribs.get(id); 235 if (head == null) { 236 // no merge needed 237 head = new FragmentList<>(); 238 this.contribs.put(id, head); 239 } 240 head.add(contrib); 241 return head; 242 } 243 244 protected FragmentList<T> removeFragment(String id, T contrib, boolean useEqualsMethod) { 245 FragmentList<T> head = contribs.get(id); 246 if (head == null) { 247 return null; 248 } 249 if (head.remove(contrib, useEqualsMethod)) { 250 if (head.isEmpty()) { 251 contribs.remove(id); 252 } 253 return head; 254 } 255 return null; 256 } 257 258 /** 259 * @since 9.3 260 */ 261 public synchronized Map<String, T> toMap() { 262 return contribs.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> e.getValue().merge(this))); 263 } 264 265 public static class FragmentList<T> extends Fragment<T> { 266 267 public FragmentList() { 268 super(null); 269 prev = this; 270 next = this; 271 } 272 273 public boolean isEmpty() { 274 return next == null; 275 } 276 277 public T merge(ContributionFragmentRegistry<T> reg) { 278 T mergedValue = object; 279 if (mergedValue != null) { 280 return mergedValue; 281 } 282 Fragment<T> p = next; 283 if (p == this) { 284 return null; 285 } 286 mergedValue = reg.isSupportingMerge() ? reg.clone(p.object) : p.object; 287 p = p.next; 288 while (p != this) { 289 if (reg.isSupportingMerge()) { 290 reg.merge(p.object, mergedValue); 291 } else { 292 mergedValue = p.object; 293 } 294 p = p.next; 295 } 296 object = mergedValue; 297 return mergedValue; 298 } 299 300 public final void add(T contrib) { 301 insertBefore(new Fragment<>(contrib)); 302 object = null; 303 } 304 305 public final void add(Fragment<T> fragment) { 306 insertBefore(fragment); 307 object = null; 308 } 309 310 public boolean remove(T contrib) { 311 return remove(contrib, false); 312 } 313 314 /** 315 * @since 5.6 316 */ 317 public boolean remove(T contrib, boolean useEqualsMethod) { 318 Fragment<T> p = next; 319 while (p != this) { 320 if (useEqualsMethod && p.object != null && p.object.equals(contrib) 321 || !useEqualsMethod && p.object == contrib) { 322 p.remove(); 323 object = null; 324 return true; 325 } 326 p = p.next; 327 } 328 return false; 329 } 330 } 331 332 public static class Fragment<T> { 333 public T object; 334 335 public Fragment<T> next; 336 337 public Fragment<T> prev; 338 339 public Fragment(T object) { 340 this.object = object; 341 } 342 343 public final void insertBefore(Fragment<T> fragment) { 344 fragment.prev = prev; 345 fragment.next = this; 346 prev.next = fragment; 347 prev = fragment; 348 } 349 350 public final void insertAfter(Fragment<T> fragment) { 351 fragment.prev = this; 352 fragment.next = next; 353 next.prev = fragment; 354 next = fragment; 355 } 356 357 public final void remove() { 358 prev.next = next; 359 next.prev = prev; 360 next = prev = null; 361 } 362 363 public final boolean hasNext() { 364 return next != null; 365 } 366 367 public final boolean hasPrev() { 368 return prev != null; 369 } 370 } 371 372}