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