1 /* 2 * This file is part of *** M y C o R e *** 3 * See http://www.mycore.de/ for details. 4 * 5 * MyCoRe is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * MyCoRe is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with MyCoRe. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 package org.mycore.datamodel.common; 20 21 import java.util.Map; 22 import java.util.concurrent.ConcurrentHashMap; 23 24 import org.mycore.common.events.MCREventHandler; 25 import org.mycore.datamodel.metadata.MCRBase; 26 import org.mycore.datamodel.metadata.MCRDerivate; 27 import org.mycore.datamodel.metadata.MCRObjectID; 28 29 /** 30 * Experimental class to improve performance on delete and import operations. 31 * You can mark object's as "will be deleted" or "will be imported". You can 32 * use this information on {@link MCREventHandler}'s to exclude those 33 * marked objects from operations which makes no sense. 34 * 35 * <h2> 36 * Current delete behavior: 37 * </h2> 38 * <ol> 39 * <li>An user delete's a parent object with 500 children.</li> 40 * <li>MyCoRe tries to delete the parent, but first, it has to delete all children.</li> 41 * <li>MyCoRe runs through each child and deletes it.</li> 42 * <li><b>BUT</b> after the deletion of <b>ONE</b> child, the parent object will be updated.</li> 43 * <li>This results in updating the parent 500 times, before its actually deleted.</li> 44 * </ol> 45 * 46 * <p> 47 * What this class tries to solve:<br> 48 * We mark the parent as "will be deleted". When a child is deleted, and the EventHandler tries 49 * to removed the child from its parent, the parent will not be updated because its "marked as 50 * deleted". 51 * </p> 52 * 53 * <h2> 54 * Current import behavior: 55 * </h2> 56 * 57 * <ol> 58 * <li>An import is started with a bunch of hierarchic objects.</li> 59 * <li>MyCoRe imports all the objects and does a "create" update.</li> 60 * <li><b>BUT</b> for each object created the parent is updated again (because a child was added)!</li> 61 * <li>This results in unnecessary updates.</li> 62 * </ol> 63 * 64 * <p> 65 * What this class tries to solve:<br> 66 * We mark all objects as "will be imported". On import, we ignore all solr index call for 67 * those objects. After the import, we delete all marks and do an solr import for all 68 * objects at once. 69 * </p> 70 * 71 * TODO: check side effects 72 * 73 * @author Matthias Eichner 74 */ 75 public class MCRMarkManager { 76 77 private static MCRMarkManager INSTANCE = null; 78 79 public enum Operation { 80 DELETE, IMPORT 81 } 82 83 private Map<MCRObjectID, Operation> marks; 84 85 private MCRMarkManager() { 86 this.marks = new ConcurrentHashMap<>(); 87 } 88 89 /** 90 * Returns the instance to the singleton {@link MCRMarkManager}. 91 * 92 * @return instance of {@link MCRMarkManager} 93 */ 94 public static MCRMarkManager instance() { 95 if (INSTANCE == null) { 96 // make it thread safe 97 synchronized (MCRMarkManager.class) { 98 if (INSTANCE == null) { 99 INSTANCE = new MCRMarkManager(); 100 } 101 } 102 } 103 return INSTANCE; 104 } 105 106 /** 107 * Marks a single object with the given operation. 108 * 109 * @param mcrId the mycore object identifier 110 * @param operation the operation 111 * @return the previous Operation associated with the mycore identifier or null 112 */ 113 public Operation mark(MCRObjectID mcrId, Operation operation) { 114 return this.marks.put(mcrId, operation); 115 } 116 117 /** 118 * Removes the current mark for the given mycore identifier. 119 * 120 * @param mcrId the object where the mark should be removed 121 */ 122 public void remove(MCRObjectID mcrId) { 123 this.marks.remove(mcrId); 124 } 125 126 /** 127 * Checks if the object is marked. 128 * 129 * @param mcrId the mcr identifier 130 * @return true if its marked 131 */ 132 public boolean isMarked(MCRObjectID mcrId) { 133 return this.marks.containsKey(mcrId); 134 } 135 136 /** 137 * Checks if the given base object is marked. If base is an instance of MCRDerivate, this method checks also if the 138 * linked object is marked. 139 * 140 * @param base the mycore object 141 * @return true if its marked 142 */ 143 public boolean isMarked(MCRBase base) { 144 if (base instanceof MCRDerivate) { 145 MCRDerivate derivate = (MCRDerivate) base; 146 return isMarked(derivate.getId()) || isMarked(derivate.getOwnerID()); 147 } 148 return isMarked(base.getId()); 149 } 150 151 /** 152 * Checks if the object is marked for deletion. 153 * 154 * @param mcrId the mcr identifier 155 * @return true if its marked for deletion 156 */ 157 public boolean isMarkedForDeletion(MCRObjectID mcrId) { 158 return Operation.DELETE.equals(this.marks.get(mcrId)); 159 } 160 161 /** 162 * Checks if the derivate or the corresponding mycore object is 163 * marked for deletion. 164 * 165 * @return true if one of them is marked for deletion 166 */ 167 public boolean isMarkedForDeletion(MCRDerivate derivate) { 168 return isMarkedForDeletion(derivate.getId()) || isMarkedForDeletion(derivate.getOwnerID()); 169 } 170 171 /** 172 * Checks if the object is marked for import. 173 * 174 * @param mcrId the mcr identifier 175 * @return true if its marked for import 176 */ 177 public boolean isMarkedForImport(MCRObjectID mcrId) { 178 return Operation.IMPORT.equals(this.marks.get(mcrId)); 179 } 180 181 }