001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu 011 */ 012package org.nuxeo.runtime.model; 013 014import java.util.HashMap; 015import java.util.Map; 016 017/** 018 * This is a contribution registry that is managing contribution fragments and merge them as needed. The implementation 019 * will be notified through {@link #contributionUpdated(String, Object)} each time you need to store or remove a 020 * contribution. Note that contribution objects that are registered by your implementation <b>must</b> not be modified. 021 * You can see them as immutable objects - otherwise your local changes will be lost at the next update event. 022 * <p> 023 * To use it you should extends this abstract implementation and implement the abstract methods. 024 * <p> 025 * The implementation registry doesn't need to be thread safe since it will be called from synchronized methods. 026 * <p> 027 * Also, the contribution object 028 * <p> 029 * A simple implementation is: 030 * 031 * <pre> 032 * public class MyRegistry extends ContributionFragmentRegistry<MyContribution> { 033 * public Map<String, MyContribution> registry = new HAshMap<String, MyContribution>(); 034 * 035 * public String getContributionId(MyContribution contrib) { 036 * return contrib.getId(); 037 * } 038 * 039 * public void contributionUpdated(String id, MyContribution contrib, MyContribution origContrib) { 040 * registry.put(id, contrib); 041 * } 042 * 043 * public void contributionRemoved(String id, MyContribution origContrib) { 044 * registry.remove(id); 045 * } 046 * 047 * public MyContribution clone(MyContribution contrib) { 048 * MyContribution clone = new MyContribution(contrib.getId()); 049 * clone.setSomeProperty(contrib.getSomeProperty()); 050 * ... 051 * return clone; 052 * } 053 * 054 * public void merge(MyContribution src, MyContribution dst) { 055 * dst.setSomeProperty(src.getSomeProperty()); 056 * ... 057 * } 058 * } 059 * </pre> 060 * 061 * Since 5.5, if the registry does not support merging of resources, you can just override the method 062 * {@link #isSupportingMerge()} and return false, so that {@link #merge(Object, Object)} and {@link #clone()} are never 063 * called. 064 * 065 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 066 * @see SimpleContributionRegistry<T> 067 */ 068public abstract class ContributionFragmentRegistry<T> { 069 070 protected Map<String, FragmentList<T>> contribs = new HashMap<String, FragmentList<T>>(); 071 072 /** 073 * Returns the contribution ID given the contribution object 074 * 075 * @param contrib 076 * @return 077 */ 078 public abstract String getContributionId(T contrib); 079 080 /** 081 * Adds or updates a contribution. 082 * <p> 083 * If the contribution doesn't yet exists then it will be added, otherwise the value will be updated. If the given 084 * value is null the existing contribution must be removed. 085 * <p> 086 * The second parameter is the contribution that should be updated when merging, as well as stored and used. This 087 * usually represents a clone of the original contribution or a merge of multiple contribution fragments. 088 * Modifications on this object at application level will be lost on next 089 * {@link #contributionUpdated(String, Object, Object)} call on the same object id: modifications should be done in 090 * the {@link #merge(Object, Object)} method. 091 * <p> 092 * The last parameter is the new contribution object, unchanged (original) which was neither cloned nor merged. This 093 * object should never be modified at application level, because it will be used each time a subsequent merge is 094 * done. Also, it never should be stored. 095 * 096 * @param id - the id of the contribution that needs to be updated 097 * @param contrib the updated contribution object that 098 * @param newOrigContrib - the new, unchanged (original) contribution fragment that triggered the update. 099 */ 100 public abstract void contributionUpdated(String id, T contrib, T newOrigContrib); 101 102 /** 103 * All the fragments in the contribution was removed. Contribution must be unregistered. 104 * <p> 105 * The first parameter is the contribution ID that should be remove and the second parameter the original 106 * contribution fragment that as unregistered causing the contribution to be removed. 107 * 108 * @param id 109 * @param origContrib 110 */ 111 public abstract void contributionRemoved(String id, T origContrib); 112 113 /** 114 * CLone the given contribution object 115 * 116 * @param object 117 * @return 118 */ 119 public abstract T clone(T orig); 120 121 /** 122 * Merge 'src' into 'dst'. When merging only the 'dst' object is modified. 123 * 124 * @param src the object to copy over the 'dst' object 125 * @param dst this object is modified 126 */ 127 public abstract void merge(T src, T dst); 128 129 /** 130 * Returns true if merge is supported. 131 * <p> 132 * Hook method to be overridden if merge logics behind {@link #clone()} and {@link #merge(Object, Object)} cannot be 133 * implemented. 134 * 135 * @since 5.5 136 */ 137 public boolean isSupportingMerge() { 138 return true; 139 } 140 141 /** 142 * Add a new contribution. This will start install the new contribution and will notify the implementation about the 143 * value to add. (the final value to add may not be the same object as the one added - but a merge between multiple 144 * contributions) 145 * 146 * @param contrib 147 */ 148 public synchronized void addContribution(T contrib) { 149 String id = getContributionId(contrib); 150 FragmentList<T> head = addFragment(id, contrib); 151 contributionUpdated(id, head.merge(this), contrib); 152 } 153 154 /** 155 * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it 156 * should store (after re-merging contribution fragments). 157 * <p> 158 * Uses standard equality to check for old objects (useEqualsMethod == false). 159 * 160 * @param contrib 161 * @see #removeContribution(Object, boolean) 162 */ 163 public synchronized void removeContribution(T contrib) { 164 removeContribution(contrib, false); 165 } 166 167 /** 168 * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it 169 * should store (after re-merging contribution fragments). 170 * <p> 171 * Equality can be controlled from here. 172 * <p> 173 * Contributions come from the runtime that keeps exact instances, so using equality usually makes it possible to 174 * remove the exact instance that was contributed by this component (without needing to reference the component name 175 * for instance). But when unit-testing, or when registrating contributions that do not come directly from the 176 * runtime, regirties need to use the equals method defined on each contribution. 177 * 178 * @param contrib the contrib to remove 179 * @param useEqualsMethod a boolean stating that old contributions should be checked using the equals method instead 180 * of 181 * @since 5.6 182 */ 183 public synchronized void removeContribution(T contrib, boolean useEqualsMethod) { 184 String id = getContributionId(contrib); 185 FragmentList<T> head = removeFragment(id, contrib, useEqualsMethod); 186 if (head != null) { 187 T result = head.merge(this); 188 if (result != null) { 189 contributionUpdated(id, result, contrib); 190 } else { 191 contributionRemoved(id, contrib); 192 } 193 } 194 } 195 196 /** 197 * Get a merged contribution directly from the internal registry - and avoid passing by the implementation registry. 198 * Note that this operation will invoke a merge of existing fragments if needed. 199 * <p> 200 * Since 5.5, this method has made protected as it should not be used by the service retrieving merged resources 201 * (otherwise merge will be done again). If you'd really like to call it, add a public method on your registry 202 * implementation that will call it. 203 * 204 * @param id 205 * @return 206 */ 207 protected synchronized T getContribution(String id) { 208 FragmentList<T> head = contribs.get(id); 209 return head != null ? head.merge(this) : null; 210 } 211 212 /** 213 * Get an array of all contribution fragments 214 * 215 * @return 216 */ 217 @SuppressWarnings("unchecked") 218 public synchronized FragmentList<T>[] getFragments() { 219 return contribs.values().toArray(new FragmentList[contribs.size()]); 220 } 221 222 protected FragmentList<T> addFragment(String id, T contrib) { 223 FragmentList<T> head = contribs.get(id); 224 if (head == null) { 225 // no merge needed 226 head = new FragmentList<T>(); 227 this.contribs.put(id, head); 228 } 229 head.add(contrib); 230 return head; 231 } 232 233 protected FragmentList<T> removeFragment(String id, T contrib, boolean useEqualsMethod) { 234 FragmentList<T> head = contribs.get(id); 235 if (head == null) { 236 return null; 237 } 238 if (head.remove(contrib, useEqualsMethod)) { 239 if (head.isEmpty()) { 240 contribs.remove(id); 241 } 242 return head; 243 } 244 return null; 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<T>(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}