4A Server -  2.0
 All Classes Namespaces Files Functions Variables Enumerator
CoreFuncModule.java
Go to the documentation of this file.
1 /*
2  * Project: Server for annotations sharing
3  * Author: Ing. Jaroslav Dytrych idytrych@fit.vutbr.cz
4  * File: CoreFuncModule.java
5  * Description: This module implements core document, annotations and types
6  * manipulation functionality.
7  *
8  * This module is under construction. Uncommented functions will be replaced.
9  */
10 
11 /**
12  * @file CoreFuncModule.java
13  *
14  * @brief Document, annotations and types manipulation functionality.
15  */
16 
17 package cz.vutbr.fit.knot.annotations.modules;
18 
23 import cz.vutbr.fit.knot.annotations.comet.*;
27 import cz.vutbr.fit.knot.annotations.entity.*;
41 import java.net.URI;
42 import java.net.URISyntaxException;
43 import java.util.*;
44 import java.util.concurrent.locks.ReentrantLock;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47 import javax.persistence.EntityManager;
48 import javax.persistence.Query;
49 import javax.xml.xpath.XPathExpressionException;
50 import org.w3c.dom.Document;
51 
52 /**
53  * This module implements core document, annotations and types manipulation
54  * functionality.
55  *
56  * @brief Document, annotations and types manipulation functionality.
57  *
58  * @author idytrych
59  */
60 public class CoreFuncModule implements AnnotServerModule {
61 
62  /**
63  * Provider of matchers for finding fragments
64  */
66  /**
67  * Provider of matchers for finding fragments (exact matches only)
68  */
70  /**
71  * Provider of matchers for finding fragments (nearest matches only)
72  */
74  /**
75  * Provider of matchers for finding fragments (sequence iterator)
76  */
78 
79  /**
80  * Specifies whether is document usage locked
81  */
82  private static boolean documentUsageLocked = false;
83 
84  /**
85  * Constructor
86  */
87  public CoreFuncModule() {
92 
93  // create default set of matchers
94  matcherProvider.add(new Matcher(new XPathNodeIterator(), new Comparator(new ExactMethod(), Comparator.TraversingMethod.STATIC)), 0);
95  // Older method disabled - it gives worse results
96  // matcherProvider.add(new Matcher(new RecursiveNodeIterator(), new Comparator(new ExactMethod(), Comparator.TraversingMethod.CHARACTER)), 1);
97  matcherProvider.add(new Matcher(new BidirectionallyUnNestNodeIterator(), new Comparator(new ExactMethod(), Comparator.TraversingMethod.CHARACTER_BIDIRECTIONALLY)), 1);
98  matcherProvider.add(new Matcher(new BidirectionallyUnNestNodeIterator(), new Comparator(new LevenshteinMethod(0.56), Comparator.TraversingMethod.WORD, 1.44, 0.56)), 2);
99  matcherProvider.add(new Matcher(new BidirectionallyUnNestNodeIterator(), new Comparator(new LevenshteinMethod(0.83), Comparator.TraversingMethod.FIRST_AND_LAST_WORD_THEN_LETTER, 1.44, 0.56)), 3);
100  // Soundex can't handle diacritic
101  // matcherProvider.add(new Matcher(new SequenceNodeIterator(), new Comparator(new SoundexMethod(), Comparator.TraversingMethod.WORD)), 3);
102  matcherProvider.add(new Matcher(new SequenceNodeIterator(), new Comparator(new LevenshteinMethod(0.56), Comparator.TraversingMethod.WORD, 1.44, 0.56)), 4);
103  matcherProvider.add(new Matcher(new SequenceNodeIterator(), new Comparator(new LevenshteinMethod(0.83), Comparator.TraversingMethod.FIRST_AND_LAST_WORD_THEN_LETTER, 1.44, 0.56)), 5);
104 
105  // create nearest matcher
106  nearestMatcherProvider.add(new Matcher(new BidirectionallyUnNestNodeIterator(), new Comparator(new ExactMethod(), Comparator.TraversingMethod.CHARACTER_BIDIRECTIONALLY)), 0);
107  nearestMatcherProvider.add(new Matcher(new BidirectionallyUnNestNodeIterator(), new Comparator(new LevenshteinMethod(0.56), Comparator.TraversingMethod.WORD, 1.44, 0.56)), 1);
108  nearestMatcherProvider.add(new Matcher(new BidirectionallyUnNestNodeIterator(), new Comparator(new LevenshteinMethod(0.83), Comparator.TraversingMethod.FIRST_AND_LAST_WORD_THEN_LETTER, 1.44, 0.56)), 2);
109 
110  // create sequence mathcer
111  sequenceMatcherProvider.add(new Matcher(new SequenceNodeIterator(), new Comparator(new ExactMethod(), Comparator.TraversingMethod.CHARACTER_BIDIRECTIONALLY)), 0);
112  sequenceMatcherProvider.add(new Matcher(new SequenceNodeIterator(), new Comparator(new LevenshteinMethod(0.56), Comparator.TraversingMethod.WORD, 1.44, 0.56)), 1);
113  sequenceMatcherProvider.add(new Matcher(new SequenceNodeIterator(), new Comparator(new LevenshteinMethod(0.83), Comparator.TraversingMethod.FIRST_AND_LAST_WORD_THEN_LETTER, 1.44, 0.56)), 2);
114 
115  // create exact matcher
116  exactMatcherProvider.add(new Matcher(new XPathNodeIterator(), new Comparator(new ExactMethod(), Comparator.TraversingMethod.STATIC)), 0);
117  } // CoreFuncModule()
118 
119  /**
120  * This method is called when client sent message and these message has been
121  * processed. Changes in types, annotations and in other entities are
122  * persisted after calling this method from all modules.
123  * It performs:
124  * - checking of synchronized document and synchronization
125  * - applying document modifications
126  * - checking of fragments in added and edited annotations
127  *
128  * @param requestInfo Informations about client request
129  * @return String with messages for client or empty string
130  */
131  @Override
132  public String processRequestBeforePersist(RequestInfo requestInfo) {
133  String retStr = "";
134 
135  // Synchronize document and generate related text modifications
136  if (requestInfo.getSyncDocumentData() != null) {
137  synchronizeDocument(requestInfo);
138  }
139 
140  // Resynchronize document and generate related text modifications
141  if (requestInfo.getResyncDocContent() != null) {
142  reSynchronizeDocument(requestInfo);
143  }
144 
145  // Apply document modifications (update document)
146 
147  try{
148  retStr = retStr + updateDocument(requestInfo);
149  }catch(Exception ex){
150  // unlock for change
151  AppBean.getLockMaster().getDocumentLock(requestInfo.getSession().getSyncDocument().getId()).unlockForChange();
152  Logger.getLogger(DrupAuthServlet.class.getName()).log(Level.SEVERE, "Exception during document update:", ex.getMessage());
153  }
154 
155  // Check, if fragments in added and edited annotations are corresponding.
156  // Remove bad fragments from added annotations.
157  // Remove bad edited annotations (don't save changes).
158  checkAddedAndEdited(requestInfo);
159 
160  //find and add to edited annotations that will be affected by deleting some annotation
161  getAnnotsForUpdate(requestInfo.getFlier().getRemovedAnnotations(),requestInfo.getFlier().getEditedAnnotations());
162 
163  // User can also have annotations that he is not subscribed to (target
164  // of the annotation link). This annotation must be provided with IDs
165  // of annotations that carries the link to it in attribute. IDs have to be stored
166  // before persist when all links exist. List of IDs is processed after persist
167  // during flier processing (currently String messagesFromFlier(Flier, EditorSession)).
168  // If user is not subscribed to the annotation type, the list IDs are insppected
169  // and if match is found, annotation is removed from the client application.
170  Flier flier = requestInfo.getFlier();
171  if (flier != null) {
172  if (flier.getRemovedAnnotations() != null && !flier.getRemovedAnnotations().isEmpty()) {
173  List<Integer> IDs = new ArrayList<Integer>();
174  Iterator<Annotation> removedAnnotIt = flier.getRemovedAnnotations().iterator();
175  while (removedAnnotIt.hasNext()) {
176  Annotation remAnnot = removedAnnotIt.next();
177  IDs.clear();
178  findLinkingAnnotationsIds(remAnnot, IDs);
179  if (!IDs.isEmpty()) {
180  remAnnot.addAllLinkedByBeforeRemove(IDs);
181  }
182  }
183  }
184  }
185 
186  return retStr;
187  } // processRequestBeforePersist()
188 
189  /**
190  * This method is called when client sent message, these message has been
191  * processed and changes in entities are persisted. Composed response is sent
192  * to client after calling this method from all modules.
193  * It performs:
194  * - actualization of language in session (set language according to database)
195  * - creating of synchronized message - creating message with queried types
196  * - sending annotations to client after synchronizing or request
197  * - sending just added types to client
198  *
199  * @param requestInfo Informations about client request
200  * @param persistFailed True if changes not persisted, false otherwise.
201  * @return String with messages for client or empty string
202  */
203  @Override
204  public String processRequestAfterPersist(RequestInfo requestInfo, boolean persistFailed) {
205  String retStr = "";
206 
207  // Actualize language in session according to database
208  if (requestInfo.getSettings() != null && !requestInfo.getSettings().isEmpty()) {
209  requestInfo.getSession().actualizeAllCachedSettings();
210  }
211 
212  // Synchronized message
213  if (requestInfo.getSyncDocumentData() != null && requestInfo.getSyncDocument() != null) {
214  AnnotDocument doc = requestInfo.getSyncDocument();
215  requestInfo.getSession().initDocumentUsageAndTMNumber();
216  if (requestInfo.getSession().getProtocolLOD() < Constants.PROTOCOL_LOD_V2) {
217  retStr = retStr + "<synchronized resource=\"" + AppBean.getBaseDocumentUri() + doc.getId()
218  + "\"/>";
219  } else {
220  retStr = retStr + "<synchronized resource=\"" + AppBean.getBaseDocumentUri() + doc.getId()
221  + "\" lastModification=\"" + AppBean.getLastDocModification(doc.getId()) + "\"/>";
222  }
223  }
224 
225  // update users data in his other sessions
226  if (!persistFailed && requestInfo.getSession().getUser() != null
227  && (!requestInfo.getJoinedGroups().isEmpty() || requestInfo.getLeavedGroups().isEmpty())) {
228  AppBean.refreshUsersInSessions(requestInfo.getSession().getUser());
229  }
230 
231  if (canBeDocumentModified(requestInfo)) {
232  // Check, if fragments in existing annotations are corresponding.
233  // Set status of all fragments which is not corresponding to bad.
234  // Notify all partially or fully orphaned annotations to user.
235  updateAllExisting(requestInfo);
236  }
237 
238  if (requestInfo.getFlier() != null && requestInfo.getFlier().getAutoUpdatedAnnotations() != null
239  && !requestInfo.getFlier().getAutoUpdatedAnnotations().isEmpty()) {
240  // save updated annotations into DB
241  boolean pFailed = Persister.persistAUpdAnnot(requestInfo);
242  if (pFailed) { // if persist failed
243  int langNum = requestInfo.getSession().getLanguageNum();
244  int lod = requestInfo.getSession().getProtocolLOD();
245  requestInfo.addError(lod, langNum, Localisation.ERROR_59_AUTO_UPDATE_FAILED);
246  // annotations will be updated later
247  requestInfo.getFlier().setAutoUpdatedAnnotations(new ArrayList<Annotation>());
248  }
249  }
250 
251  // Create message with queried types
252  retStr = retStr + getQueriedTypes(requestInfo);
253 
254  // Create message with annotations after synchronization or request
255  if (requestInfo.getSyncDocument() != null || requestInfo.getReloadAll()) {
256  if (requestInfo.getSession().getSyncDocument() != null) {
257  retStr = retStr + getAllInterestingAnnotations(requestInfo);
258  }
259  }
260 
261  // Create message with just added types
262  if (!requestInfo.getFlier().getAddedTypes().isEmpty()
263  && requestInfo.getSession().getProtocolLOD() != Constants.PROTOCOL_LOD_V2) {
264  boolean withAnc = true; // types will be send with ancestors
265  if (requestInfo.getSession().getProtocolLOD() < Constants.PROTOCOL_LOD_V1_1) {
266  withAnc = false; // old client - types will be send without ancestors
267  }
268  Iterator<AnnotType> tIter = requestInfo.getFlier().getAddedTypes().iterator();
269 
270  retStr = retStr + "<types><add>";
271  while (tIter.hasNext()) {
272  AnnotType annotType = tIter.next();
273  retStr = retStr + annotType.toXMLString(withAnc);
274  }
275  retStr = retStr + "</add></types>";
276 
277  }
278 
279  // Create message with attributes from ontology
280  if (requestInfo.getQueryTypeAttOnto() != null) {
281  retStr = retStr + getAttrOnto(requestInfo);
282  }
283 
284  if(requestInfo.getSubscriptionQuery() != null){
285  retStr = retStr + getQueriedSubscriptions(requestInfo);
286  }
287 
288  // ***** Subscriptions - change *****
289  if(requestInfo.getUnsubscribeList() != null && !requestInfo.getUnsubscribeList().isEmpty()){
290  // delete
291  Iterator<Subscription> chngedSubsIt = requestInfo.getUpdatedSubscriptions().iterator();
292  while(chngedSubsIt.hasNext()){
293  Subscription actualSubscription = chngedSubsIt.next();
294  }
295  }
296 
297  // ***** Subscriptions - delete *****
298  if(requestInfo.getUnsubscribeList() != null && !requestInfo.getUnsubscribeList().isEmpty()){
299  // delete
300  Iterator<Subscription> deletedSubsIt = requestInfo.getUnsubscribeList().iterator();
301  while(deletedSubsIt.hasNext()){
302  Subscription actualSubscription = deletedSubsIt.next();
303 
304  if(requestInfo.getSession().isInSubscribeList(actualSubscription)){
305  requestInfo.getSession().deleteFromSubscribes(actualSubscription);
306  requestInfo.getSession().deleteFromSubscribeList(actualSubscription);
307  }
308  }
309  }
310 
311  // ***** Subscriptions - new *****
312  if(requestInfo.getNewSubscriptions() != null && !requestInfo.getNewSubscriptions().isEmpty()){
313  // add
314  Iterator <Subscription> subsIt = requestInfo.getNewSubscriptions().iterator();
315  while(subsIt.hasNext()){
316 
317  Subscription currentSubs = subsIt.next();
318  StringBuilder newSubs = new StringBuilder();
319  newSubs.append("<subscriptionCreated tmpId=\"");
320  newSubs.append(currentSubs.getTmpId().toString());
321  newSubs.append("\" uri=\"");
322  newSubs.append(AppBean.getBaseSubscriptionUri());
323  newSubs.append(currentSubs.getId());
324  newSubs.append("\"/>");
325  retStr = retStr + newSubs.toString();
326  }
327  }
328 
329  // ***** Subscriptions - add *****
330  if(requestInfo.getSubscribeList() != null && !requestInfo.getSubscribeList().isEmpty()){
331  // add
332  Iterator <Subscription> subsIt = requestInfo.getSubscribeList().iterator();
333  while(subsIt.hasNext()){
334  Subscription currentSubs = subsIt.next();
335  if(!requestInfo.getSession().isInSubscribeList(currentSubs)){
336  requestInfo.getSession().addToSubscribes(currentSubs);
337  requestInfo.getSession().addToSubscribeList(currentSubs);
338  }
339  }
340  }
341 
342  // get confirmed suggestions
343  ArrayList<Annotation> confirmedSugs = new ArrayList<Annotation>();
344  if (requestInfo.getConfirmedSuggestions() != null && !requestInfo.getConfirmedSuggestions().isEmpty()) {
345  String confStr = "";
346  Iterator<SuggestionLogEntry> slIt = requestInfo.getConfirmedSuggestions().iterator();
347  while (slIt.hasNext()) {
348  SuggestionLogEntry sle = slIt.next();
349  Annotation confirmed = sle.getConfirmedVersion();
350  confirmedSugs.add(confirmed);
351  }
352  }
353 
354  //Make message for just added annotations
355  if( requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2
356  && requestInfo.getFlier().getAddedAnnotations() != null
357  && !requestInfo.getFlier().getAddedAnnotations().isEmpty()){
358 
359  String addedStr = "";
360  ArrayList<Annotation> addApprovedAnnotations = new ArrayList<Annotation>();
361  ArrayList<Annotation> unapprovedAnnotations = new ArrayList<Annotation>();
362  Iterator<Annotation> annotIter = requestInfo.getFlier().getAddedAnnotations().iterator();
363  EditorSession session = requestInfo.getSession();
364 
365  while (annotIter.hasNext()) { // for each added annotation
366  Annotation annot = annotIter.next();
367  if (session.isSubscribed(annot, getUserSources(annot.getUser()))) { // only subscribed annotations
368  addApprovedAnnotations.add(annot); // add approved anotation to approved list
369  } else {
370  unapprovedAnnotations.add(annot); // other annotations add to unapproved list
371  }
372  }
373 
374  addApprovedAnnotations = getRelatedAnnotations(addApprovedAnnotations, unapprovedAnnotations, session);
375 
376  // Make message for added annotations
377  Iterator<Annotation> approvedIter = addApprovedAnnotations.iterator();
378  ArrayList<Annotation> printedAn = new ArrayList<Annotation>();
379 
380  while (approvedIter.hasNext()) { // for each approved annotation
381  Annotation annot = approvedIter.next();
382  if (printedAn.contains(annot)) {
383  continue;
384  }
385  if (confirmedSugs.contains(annot)) {
386  continue;
387  }
388  printedAn.add(annot);
389  if(requestInfo.getFlier().isInJustAdded(annot.getURIV2())){
390  String tmpUri = requestInfo.getFlier().getTmpUri(annot.getURIV2());
391  if(tmpUri != null){
392  addedStr = addedStr + "<annotation tempUri=\"" + tmpUri + "\" servUri=\"" + annot.getURIV2() + "\"/>";
393  }
394 
395  ArrayList<String> nestedAnnotURIs = getAllNestedIdentificators(annot);
396  Iterator<String> nestedAnnotURIIt = nestedAnnotURIs.iterator();
397  while(nestedAnnotURIIt.hasNext()){
398  String nestedUri = nestedAnnotURIIt.next();
399  String tmpNestedUri = requestInfo.getFlier().getTmpUri(nestedUri);
400 
401  if(tmpNestedUri != null){
402  addedStr = addedStr + "<annotation tempUri=\"" + tmpNestedUri + "\" servUri=\"" + nestedUri + "\"/>";
403  }
404  }
405  }
406  }
407 
408  if(!addedStr.isEmpty()){
409  retStr = retStr + "<annotationsCreated>" + addedStr + "</annotationsCreated>";
410  }
411  }
412 
413  // Create message for just confirmed suggestions
414  if (!confirmedSugs.isEmpty() && requestInfo.getSession().getProtocolLOD() >= Constants.PROTOCOL_LOD_V2) {
415  String confStr = "";
416  String autoConfStr = "";
417  String removeSugsStr = "";
418  Iterator<Annotation> csIt = confirmedSugs.iterator();
419  ArrayList<String> attFilter = requestInfo.getSession().getAttFilter();
420  int lNum = requestInfo.getSession().getLanguageNum();
421  Boolean KBRefMode = requestInfo.getSession().getKBRefMode();
422  while (csIt.hasNext()) {
423  Annotation confirmed = csIt.next();
424  if (requestInfo.isAutoconfirmRequested()) { // all automatically confirmed
425  confStr = confStr + confirmed.toXMLStringV2(attFilter, lNum, KBRefMode);
426  } else { // some manually confirmed
427  if (requestInfo.getAutoconfirmedSuggestions().contains(new SuggestionLogEntry(confirmed.getTmpId()))) {
428  autoConfStr = autoConfStr + confirmed.toXMLStringV2(attFilter, lNum, KBRefMode);
429  removeSugsStr = removeSugsStr + "<suggestion uri=\"" + AppBean.getBaseUri() + "/sugg/" + confirmed.getTmpId() + "\"/>";
430  } else {
431  confStr = confStr + "<suggestion suggUri=\"" + AppBean.getBaseUri() + "/sugg/"
432  + confirmed.getTmpId() + "\" servUri=\"" + confirmed.getURIV2() + "\"/>";
433  }
434  }
435  }
436  if (!confStr.isEmpty()) {
437  if (requestInfo.isAutoconfirmRequested()) { // all automatically confirmed
438  retStr = retStr + "<addAnnotations>" + confStr + "</addAnnotations>";
439  } else {
440  retStr = retStr + "<suggestionsConfirmed>" + confStr + "</suggestionsConfirmed>";
441  if (!autoConfStr.isEmpty()) { // add automatically confirmed ones
442  String cometStr = "<addAnnotations>" + autoConfStr + "</addAnnotations>"
443  + "<removeSuggestions>" + removeSugsStr + "</removeSuggestions>";
444  requestInfo.getSession().addMessageTS(cometStr);
445  requestInfo.getSession().notifyComet();
446  }
447  }
448  }
449  }
450 
451  return retStr;
452  } // processRequestAfterPersist()
453 
454  /**
455  * This method is called for each sleeping comet handler when he receives
456  * flier (part of informations about this or another client request).
457  *
458  * @param flier Flier, which has been sent to all comet handlers.
459  * @param session Session associated with comet handler.
460  * @return If this handler hasn't react to this flier (not interested) return
461  * null, else return messages for client in single string.
462  */
463  @Override
464  public String messagesFromFlier(Flier flier, EditorSession session) {
465  String retStr = "";
466 
467  // check minimal version of protocol
468  boolean proto11 = session.getProtocolLOD() > 1;
469  boolean normalTMadded = false;
470 
471  // ***** Text modifications *****
472  if (flier.getCreatedInSessId() != null
473  && flier.getCreatedInSessId() != session.getSessionId() &&
474  !flier.getTextModifications().isEmpty()) {
475  // if this is not handler of client which generated modifications
476  String tModStr = "";
477  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
478  normalTMadded = true;
479  tModStr = "<modification id=\"" + flier.getTextModificationSetID() + "\">";
480  }
481  Iterator<TextModification> modIter = flier.getTextModifications().iterator();
482  if (session.getProtocolLOD() <= Constants.PROTOCOL_LOD_V1_1) {
483  while (modIter.hasNext()) { // for each text modification
484  TextModification tMod = modIter.next();
485  tModStr = tModStr + tMod.toXMLString(); // create message
486  }
487  }
488  else{
489  while (modIter.hasNext()) { // for each text modification
490  TextModification tMod = modIter.next();
491  tModStr = tModStr + tMod.toXMLStringV2(); // create message
492  }
493  }
494  retStr = retStr + tModStr;
495  }
496 
497  // ***** Reverse text modifications *****
498  if (flier.getCreatedInSessId() != null
499  && flier.getCreatedInSessId() == session.getSessionId()
500  && !flier.getReverseTextModifications().isEmpty()) {
501  String tModStr = "";
502  if(!normalTMadded && session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
503  tModStr = "<modification id=\"" + flier.getTextModificationSetID() + "\">";
504  }
505  Iterator<TextModification> modIter = flier.getReverseTextModifications().iterator();
506  if (session.getProtocolLOD() <= Constants.PROTOCOL_LOD_V1_1) {
507  while (modIter.hasNext()) { // for each text modification
508  TextModification tMod = modIter.next();
509  tModStr = tModStr + tMod.toXMLString(); // create message
510  }
511  }
512  else{
513  while (modIter.hasNext()) { // for each text modification
514  TextModification tMod = modIter.next();
515  tModStr = tModStr + tMod.toXMLStringV2(); // create message
516  }
517  }
518  retStr = retStr + tModStr;
519  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
520  normalTMadded = false;
521  retStr = retStr + "</modification>";
522  }
523  }
524 
525  if(normalTMadded && session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
526  retStr = retStr + "</modification>";
527  }
528 
529  // ***** Types of annotations *****
530  boolean withAnc = true; // types will be send with ancestors
532  withAnc = false; // old client - types will be send without ancestors
533  }
534  // if a user hasn't queried for any type yet, the update message is useless for him
535  if (session.getQueriedTypes() == null || session.getQueriedTypes().isEmpty()) {
536  String addedTStr = "";
537  Iterator<AnnotType> typeIter = flier.getAddedTypes().iterator();
538  while (typeIter.hasNext()) { // for each added type of annotation
539  AnnotType type = typeIter.next();
540  // send type only if client is interested = has type or subtree with new type in his cache
541  if (isTypeInGroups(type, session) && typeInClientCache(type, session.getQueriedTypes())) {
542  if (session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
543  if (session.getSessionId() != flier.getCreatedInSessId()){
544  addedTStr = addedTStr + type.toXMLStringV2(withAnc);
545  }
546  } else {
547  addedTStr = addedTStr + type.toXMLString(withAnc);
548  }
549  }
550  }
551 
552  if (!addedTStr.isEmpty()) { // if there are any added types to send
553  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
554  addedTStr = "<addTypes>" + addedTStr + "</addTypes>";
555  }else{
556  addedTStr = "<add>" + addedTStr + "</add>"; // surround with add element
557  }
558  }
559 
560  String updatedTStr = "";
561  typeIter = flier.getEditedTypes().iterator();
562  while (typeIter.hasNext()) { // for each edited type of annotation
563  AnnotType type = typeIter.next();
564  // send type only if client is interested = has type or subtree with edited type in his cache
565  if (isTypeInGroups(type, session) && typeInClientCache(type, session.getQueriedTypes())) {
566  if (session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
567  if (session.getSessionId() != flier.getCreatedInSessId()){
568  updatedTStr = updatedTStr + type.toXMLStringV2(withAnc);
569  }
570  } else {
571  updatedTStr = updatedTStr + type.toXMLString(withAnc);
572  }
573  }
574  }
575 
576  if (!updatedTStr.isEmpty()) { // if there are any changed types to send
577  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
578  updatedTStr = "<modifyTypes>" + updatedTStr + "</modifyTypes>"; // surround with change element
579  }else{
580  updatedTStr = "<change>" + updatedTStr + "</change>"; // surround with change element
581  }
582  }
583 
584  String deletedTStr = "";
585  typeIter = flier.getRemovedTypes().iterator();
586  while (typeIter.hasNext()) { // for each deleted type of annotation
587  if(session.getSessionId() != flier.getCreatedInSessId()){
588  AnnotType type = typeIter.next();
589  // Send type only if the client is interested (has type in cache)
590  if (isTypeInGroups(type, session) && typeInClientCache(type, session.getQueriedTypes())) {
591  deletedTStr = deletedTStr + type.toXMLStringWA(withAnc);
592  }
593  }
594  }
595  if (!deletedTStr.isEmpty()) { // if there are any removed types to send
596 
597  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
598  deletedTStr = "<removeTypes>" + deletedTStr + "</removeTypes>";
599  }else{
600  deletedTStr = "<remove>" + deletedTStr + "</remove>"; // surround with remove element
601  }
602  }
603 
604  String typesStr = addedTStr + updatedTStr + deletedTStr; // compose add, change and remove
605  if (!typesStr.isEmpty()) { // if there are any element with type to send
606  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
607  retStr = typesStr;
608  }else{
609  retStr = "<types>" + typesStr + "</types>"; // create message
610  }
611  }
612  }
613 
614  // ***** Annotations *****
615  // ** Make messages for added annotations
616  String addedStr = makeAddedAnnotMsg(flier.getAddedAnnotations(),session,flier,proto11);
617 
618  // ** Make messages for deleted annotations
619  String removedStr = makeDeletedAnnotMsg(flier.getRemovedAnnotations(),flier.getRemovedNestedAnnotations(),session,proto11);
620 
621 
622  // ** Make message for edited annotations and auto updated annotations
623  ArrayList<Annotation> changedAndAutoUpd = addWithoutDuplicity(flier.getEditedAnnotations(), flier.getAutoUpdatedAnnotations());
624  String editedStr = makeChangedAnnotMsg(changedAndAutoUpd,session,proto11);
625 
626  //Wrap messages into annotation tag
627  String annotStr = addedStr + editedStr + removedStr; // compose add, change and remove
628  if (!annotStr.isEmpty()) { // if there are any element with annotation to send
630  retStr = retStr + "<annotations>" + annotStr + "</annotations>";
631  }else if((session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2)
632  && session.getSessionId() != flier.getCreatedInSessId()){
633  if(addedStr != null && !addedStr.isEmpty()){
634  retStr += addedStr;
635  }
636 
637  if(removedStr != null && !removedStr.isEmpty()){
638  retStr += removedStr;
639  }
640 
641  if(editedStr != null && !editedStr.isEmpty()){
642  retStr += editedStr;
643  }
644  }
645  }
646 
647  // ***** Subscriptions *****
648  // updated subscriptions
649  if(session != null && flier.getCreatedInSessId() != null
650  && !flier.getCreatedInSessId().equals(session.getSessionId())
651  && flier.getChangedSubscriptions() != null
652  && !flier.getChangedSubscriptions().isEmpty()){
653 
654  StringBuilder chngedSubsMesg = new StringBuilder();
655 
656  // for all changed subscriptions
657  Iterator<Subscription> changedSubsIt = flier.getChangedSubscriptions().iterator();
658  while(changedSubsIt.hasNext()){
659  Subscription actualSubscription = changedSubsIt.next();
660  if(session.isInSubscribeList(actualSubscription)){
661  Subscription oldSubscription = session.getSubscriptionFromList(actualSubscription.getId());
662  // update informations about subscription
663  session.deleteFromSubscribes(oldSubscription);
664  session.addToSubscribes(actualSubscription);
665 
666  // make message for client
667  chngedSubsMesg.append(actualSubscription.toXMLStringV2("modifySubscription"));
668  }
669  }
670 
671  retStr += chngedSubsMesg.toString();
672  }
673 
674  return retStr;
675  } // messagesFromFlier()
676 
677  /**
678  * Finds all annotations linking this patricular annotation using database query.
679  * Result of the search is stored to list "closed" and is later added to the
680  * removed annotation. The method is nescessery for removing annotations that
681  * user client have but is not subscribed to, e.g. target of the annotation link.
682  *
683  * @param remAnnot Initial removed annotation that is possible target of the annotation link
684  * @param closed list of unique IDs of all found linking annotations
685  */
686  private void findLinkingAnnotationsIds(Annotation remAnnot, List<Integer> closed) {
687  Object[] params = {"linked", remAnnot.getId()};
688  @SuppressWarnings("unchecked")
689  List<LinkedAnnotationAttribute> attrList = AppBean.getPersistenceManager().queryDB("Attribute.findByLinked", params);
691  if (attrList != null) {
692  Iterator<LinkedAnnotationAttribute> linkedAttIt = attrList.iterator();
693  while (linkedAttIt.hasNext()) {
694  found = linkedAttIt.next();
695  if (!closed.contains(found.getAnnotation())) {
696  closed.add(found.getAnnotation());
698  }
699  }
700  }
701  }
702 
703  /**
704  * Client has a cache containing a history of type tree queries. It can
705  * request for the whole types tree or any subtree. Flier contains
706  * added and modified types. Method decides if the user will receive type
707  * from the flier for cache update. Type is send only when user previously
708  * asked for the subtree that the new/modified type is a part of.
709  *
710  * @param type New modified or added type obtained from a flier
711  * @param queries History of type query request strings, may contain wildcard char
712  * @return True if the user has the modified type or subtree containing new type in cache, false otherwise
713  */
714  private boolean typeInClientCache(AnnotType type, ArrayList<String> queries) {
715  // if user asked for the whole type tree, there is no need to test subtrees
716  // (older version of the client sends a "**" string)
717  if (queries.contains("*") || queries.contains("**")) {
718  return true;
719  }
720 
721  // an annotation type can be identified by an URI or a linearized name
722  // (type->subtype->subtype)
723  // an asterisk in a queried type string represents a wildcard
724  String typeName = type.getLinearizedName();
725  String typeUri = type.getUri();
726 
727  Iterator queriedTypesIt = queries.iterator();
728  String queriedType;
729  while (queriedTypesIt.hasNext()) {
730  queriedType = (String)queriedTypesIt.next();
731  if (queriedType.startsWith("http://") || queriedType.startsWith("https://")) {
732  // use new types URI for matching
733  if (containsMoreThanTwoAsterisks(typeUri, queriedType)) {
734  // more than one wildcard character
735  if (typeUri.matches(queriedType.replace("*", ".*"))) {
736  return true;
737  }
738  } else if (queriedType.endsWith("*")) {
739  // one wildcard character at the end of the query string only,
740  // can be removed
741  if (typeUri.startsWith(queriedType.replace("*", ""))) {
742  return true;
743  }
744  } else {
745  // one wildcard character somewhere in the uri or none
746  if (typeUri.matches(queriedType.replace("*", ".*"))) {
747  return true;
748  }
749  }
750 
751  } else {
752  // simply use regex on the linearized type name
753  if (typeName.matches(queriedType.replace("*", ".*"))) {
754  return true;
755  }
756  }
757  }
758 
759  // no match found, the type or the type's supertree wasn't requested
760  // by the user yet
761  return false;
762  }
763 
764  /**
765  *
766  * Checks whether wildcard contains more than one asterisk
767  *
768  * @param pattern Pattern string
769  * @param wildcard Wildcard string
770  * @return True if wildcard contains more than one asterisk, false otherwise
771  */
772  private boolean containsMoreThanTwoAsterisks(String pattern, String wildcard) {
773  return pattern.length() - wildcard.replace("*", "").length() >= 2;
774  }
775 
776  /**
777  * Method takes all added annotations and detects whether client require these
778  * annotations. Then method make message from required annotations.
779  *
780  * @param addedAnnotations array of added annotations
781  * @param session informations about client session
782  * @param flier Flier, which has been sent to all comet handlers.
783  * @param proto11 minimal protocol version
784  * @return message for client
785  */
786  private String makeAddedAnnotMsg(ArrayList<Annotation> addedAnnotations,EditorSession session, Flier flier, boolean proto11){
787  String addedStr = "";
788  ArrayList<Annotation> addApprovedAnnotations = new ArrayList<Annotation>();
789  ArrayList<Annotation> unapprovedAnnotations = new ArrayList<Annotation>();
790  Iterator<Annotation> annotIter = addedAnnotations.iterator();
791 
792  while (annotIter.hasNext()) { // for each added annotation
793  Annotation annot = annotIter.next();
794  if (session.isSubscribed(annot, getUserSources(annot.getUser()))) { // only subscribed annotations
795  addApprovedAnnotations.add(annot); // add approved anotation to approved list
796  } else {
797  unapprovedAnnotations.add(annot); // other annotations add to unapproved list
798  }
799  }
800 
801  addApprovedAnnotations = getRelatedAnnotations(addApprovedAnnotations, unapprovedAnnotations, session);
802 
803  //Make message for added annotations
804  Iterator<Annotation> approvedIter = addApprovedAnnotations.iterator();
805  ArrayList<Annotation> printedAn = new ArrayList<Annotation>();
806  ArrayList<String> attFilter = session.getAttFilter();
807  int lNum = session.getLanguageNum();
808  Boolean KBRefMode = session.getKBRefMode();
809 
810  while (approvedIter.hasNext()) { // for each approved annotation
811  Annotation annot = approvedIter.next();
812  if (printedAn.contains(annot)) {
813  continue;
814  }
815  printedAn.add(annot);
816  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
817  if(flier.isInJustAdded(annot.getURIV2())){
818  addedStr = addedStr + annot.toXMLStringV2(attFilter, lNum, KBRefMode);
819  }
820  }else{
821  addedStr = addedStr + annot.toXMLString(proto11);
822  }
823  }
824  if (!addedStr.isEmpty()) { // if there are any added annotations to send
825  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
826  addedStr = "<addAnnotations>" + addedStr + "</addAnnotations>"; // surround with add element
827  }else{
828  addedStr = "<add>" + addedStr + "</add>";
829  }
830  }
831 
832  return addedStr;
833  }
834 
835  /**
836  * Method takes all deleted annotations and detects whether client require these
837  * annotations. Then method make message from required annotations.
838  *
839  * @param deletedAnnotations array of deleted annotations
840  * @param deletedNestedAnnotations array of deleted nested annotations
841  * @param session informations about client session
842  * @param proto11 minimal protocol version
843  * @return message for client
844  */
845  private String makeDeletedAnnotMsg(ArrayList<Annotation> deletedAnnotations, ArrayList<Annotation> deletedNestedAnnotations, EditorSession session, boolean proto11){
846  String removedStr = "";
847  Iterator<Annotation> deletedIter = deletedAnnotations.iterator();
848 
849  while (deletedIter.hasNext()) { // for each removed annotation
850  Annotation annot = deletedIter.next();
851  addNestedToRemoved(annot, deletedNestedAnnotations);
852  if (session.isSubscribed(annot, getUserSources(annot.getUser())) || isSubscribedToLinkingAnnot(session, annot, deletedAnnotations)) { // if annotation come under client subscriptions
853  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
854  removedStr = removedStr + "<annotation uri=\"" + annot.getURIV2() + "\"/>";
855  }else{
856  removedStr = removedStr + annot.toXMLString(proto11);
857  }
858  }
859  }
860 
861  deletedIter = deletedNestedAnnotations.iterator();
862  while (deletedIter.hasNext()) { // for each removed nested annotation
863  Annotation annot = deletedIter.next();
864  if (session.isSubscribed(annot, getUserSources(annot.getUser())) || isSubscribedToLinkingAnnot(session, annot, deletedNestedAnnotations)) { // if annotation come under client subscriptions
865  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
866  removedStr = removedStr + "<annotation uri=\"" + annot.getURIV2() + "\"/>";
867  }else{
868  removedStr = removedStr + annot.toXMLString(proto11);
869  }
870  }
871  }
872 
873  //Make message for deleted annotations
874  if (!removedStr.isEmpty()) { // if there are any removed annotations to send
875  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
876  removedStr = "<removeAnnotations>" + removedStr + "</removeAnnotations>";
877  }else{
878  removedStr = "<remove>" + removedStr + "</remove>"; // surround with remove element
879  }
880  }
881 
882  return removedStr;
883  }
884 
885  /**
886  * Checks if removed annotation is linked by annotation that user client is
887  * subscribed to. The client can display annotations that the user is not
888  * subscribed to in case that the annotation is a target of the annotation link.
889  *
890  * @param session User session
891  * @param remAnnot Removed annotation which is inspected for linking annotations and their subscribtion status
892  * @param deletedAnnotations List of all removed annotations obtained from a flier
893  * @return True if the user is subscribed to any of the removed annotations linking annotation
894  */
895  private boolean isSubscribedToLinkingAnnot(EditorSession session, Annotation remAnnot, ArrayList<Annotation> deletedAnnotations) {
896  if (remAnnot.getLinkedByBeforeRemove() == null || remAnnot.getLinkedByBeforeRemove().isEmpty()) {
897  return false;
898  }
899  Iterator<Integer> linkingAnnotIdIt = remAnnot.getLinkedByBeforeRemove().iterator();
900  while (linkingAnnotIdIt.hasNext()) {
901  Integer id = linkingAnnotIdIt.next();
902  Object[] params = {"id", id};
903  @SuppressWarnings("unchecked")
904  List<Annotation> annotList = AppBean.getPersistenceManager().queryDB("Annotation.findById", params);
905  // annotation was found in the database
906  if (annotList != null && !annotList.isEmpty()) {
907  Annotation linkingAnnot = annotList.get(0);
908  if (session.isSubscribed(linkingAnnot, getUserSources(linkingAnnot.getUser()))) {
909  return true;
910  }
911  // annotation wasn't found in the database, try to look it up in the list
912  // of recently deleted annotations
913  } else {
914  if (deletedAnnotations != null && !deletedAnnotations.isEmpty()) {
915  Iterator<Annotation> deletedAnnotIt = deletedAnnotations.iterator();
916  while (deletedAnnotIt.hasNext()) {
917  Annotation delAnnot = deletedAnnotIt.next();
918  if (delAnnot.getId() == id) {
919  if (session.isSubscribed(delAnnot, getUserSources(delAnnot.getUser()))) {
920  return true;
921  }
922  }
923  }
924  }
925  }
926  }
927  return false;
928  }
929 
930  /**
931  * Method takes all changed annotations and detects whether client require these
932  * annotations. Then method make message from required annotations.
933  *
934  * @param changedAnnotations array of changed annotations
935  * @param session informations about client session
936  * @param proto11 minimal protocol version
937  * @return message for client
938  */
939  private String makeChangedAnnotMsg(ArrayList<Annotation> changedAnnotations,EditorSession session, boolean proto11){
940  String cahngedStr = "";
941  ArrayList<Annotation> chngApprovedAnnotations = new ArrayList<Annotation>();
942  ArrayList<Annotation> unapprovedAnnotations = new ArrayList<Annotation>();
943  Iterator<Annotation> annotIter = changedAnnotations.iterator();
944 
945  while (annotIter.hasNext()) { // for each added annotation
946  Annotation annot = annotIter.next();
947  if (session.isSubscribed(annot, getUserSources(annot.getUser()))) { // only subscribed annotations
948  chngApprovedAnnotations.add(annot); // add approved anotation to approved list
949  } else {
950  unapprovedAnnotations.add(annot); // other annotations add to unapproved list
951  }
952  }
953 
954  chngApprovedAnnotations = getRelatedAnnotations(chngApprovedAnnotations, unapprovedAnnotations, session);
955  chngApprovedAnnotations = getLinkedAnnotations(chngApprovedAnnotations);
956 
957  ArrayList<String> attFilter = session.getAttFilter();
958  int lNum = session.getLanguageNum();
959  Boolean KBRefMode = session.getKBRefMode();
960 
961  //Make message for changed annotations
962  annotIter = chngApprovedAnnotations.iterator();
963 
964  while (annotIter.hasNext()) { // for each approved annotation
965  Annotation annot = annotIter.next();
966  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
967  cahngedStr = cahngedStr + annot.toXMLStringV2(attFilter, lNum, KBRefMode);
968  }else{
969  cahngedStr = cahngedStr + annot.toXMLString(proto11);
970  }
971  }
972  if (!cahngedStr.isEmpty()) { // if there are any changed annotations to send
973  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
974  cahngedStr = "<modifyAnnotations>" + cahngedStr + "</modifyAnnotations>";
975  }else{
976  cahngedStr = "<change>" + cahngedStr + "</change>"; // surround with change element
977  }
978 
979  }
980 
981  return cahngedStr;
982  }
983 
984  /**
985  * Method merges the two arrays of annotations into a array list without duplicates.
986  *
987  * @param listOfOld first array of annotations
988  * @param listOfNew second array of annotations
989  * @return array of merged input arrays
990  */
991  private ArrayList<Annotation> addWithoutDuplicity(ArrayList<Annotation> listOfOld,ArrayList<Annotation> listOfNew){
992  if(listOfNew != null && !listOfNew.isEmpty()){
993  //if is annotations array list empty
994  if(listOfOld.isEmpty()){
995  //simply add all annotations
996  listOfOld.addAll(listOfNew);
997  }else{
998  //if annotations array list have some annotations
999  Iterator<Annotation> listOfNewIter = listOfNew.iterator();
1000 
1001  //check if old annotations list don't contains same annotation that is in new annotations array list
1002  while(listOfNewIter.hasNext()){
1003  Annotation actualAnnotation = listOfNewIter.next();
1004  if(!listOfOld.contains(actualAnnotation)){
1005  //add only different annotations
1006  listOfOld.add(actualAnnotation);
1007  }
1008  }
1009  }
1010  }
1011 
1012  return listOfOld;
1013  }
1014 
1015  /**
1016  * Adds nested annotations of the given annotation to list of removed nested
1017  * annotations
1018  *
1019  * @param annot current annotation thats nested annotation will be added to list
1020  * @param removedNested list of removed nested annotations
1021  */
1022  private void addNestedToRemoved(Annotation annot, List<Annotation> removedNested) {
1023  for (Iterator<BaseAttribute> aIt = annot.getAttributes().iterator(); aIt.hasNext();) {
1024  BaseAttribute attr = aIt.next();
1025  if (attr.getNestedAnnotation() != null) {
1026  addNestedToRemoved(attr.getNestedAnnotation(), removedNested);
1027  removedNested.add(attr.getNestedAnnotation());
1028  }
1029  }
1030  } // addNestedToRemoved()
1031 
1032  /**
1033  * Returns serialized attributes of unknown types of annotation from ontology
1034  *
1035  * @param requestInfo Informations about client request
1036  * @return Returns serialized attributes from ontology
1037  */
1038  private String getAttrOnto(RequestInfo requestInfo) {
1039  String retStr = "";
1040 
1041  // query database for attributes
1042  Object[] params = new Object[2];
1043  params[0] = "uri";
1044  params[1] = requestInfo.getQueryTypeAttOnto();
1045  List aList = null;
1046  if (requestInfo.getQueryTypeAttOnto().equals("*")) {
1047  aList = AppBean.getPersistenceManager().queryDB("TypeAttrOnto.findAll");
1048  } else {
1049  aList = AppBean.getPersistenceManager().queryDB("TypeAttrOnto.findByUserGroup", params);
1050  }
1051  if (aList != null && !aList.isEmpty()) { // if some types was found
1052  Iterator aListIt = aList.iterator();
1053  // If protocol v1.x is used
1054  if(requestInfo.getSession().getProtocolLOD() < Constants.PROTOCOL_LOD_V2){
1055  while (aListIt.hasNext()) {
1056  TypeAttrOnto attr = (TypeAttrOnto) aListIt.next();
1057  retStr = retStr + attr.toXMLString();
1058  }
1059  }
1060  //Protocol v2.x is used
1061  else{
1062  while (aListIt.hasNext()) {
1063  TypeAttrOnto attr = (TypeAttrOnto) aListIt.next();
1064  retStr = retStr + attr.toXMLStringV2();
1065  }
1066  }
1067  }
1068  // Surrounding if some attributes were serialized is used only in protocol v1.x
1069  // In protocol v2.x, surrounding is always used, even if string after serial. is empty
1070  if(requestInfo.getSession().getProtocolLOD() < Constants.PROTOCOL_LOD_V2){
1071  if (!retStr.isEmpty()) { // if some attributes serialized, surround it
1072  retStr = "<attrsFromOntology>" + retStr + "</attrsFromOntology>";
1073  }
1074  }
1075  else{
1076  retStr = "<ontologyAttributes>" + retStr + "</ontologyAttributes>";
1077  }
1078 
1079  return retStr;
1080  } // getAttrOnto()
1081 
1082  /**
1083  * Checks whether type come under user groups of logged in user
1084  *
1085  * @param annotType Checked type of annotation
1086  * @param session Client session
1087  * @return If type come under user groups of logged in user, returns true,
1088  * false otherwise
1089  */
1090  private boolean isTypeInGroups(AnnotType annotType, EditorSession session) {
1091  if (session.getUser() == null) { // if user is not logged in, there is no groups
1092  return false;
1093  }
1094  if (annotType.getGroup() == null) { // if type of annotation isn't assigned to user group
1095  return false; // it can't come under user groups
1096  }
1097  Iterator<UserGroup> grIter = session.getUser().getGroups().iterator();
1098  while (grIter.hasNext()) { // for each user group of logged in user
1099  UserGroup userGroup = grIter.next();
1100  if (annotType.getGroup().equals(userGroup)) { // if group of type is matching this group
1101  return true;
1102  }
1103  }
1104  return false;
1105  } // isTypeInGroups()
1106 
1107 
1108  /**
1109  * Creates message with types of subscriptions requested (queried) by client
1110  *
1111  * @param requestInfo Informations about client request
1112  * @return Returns subscriptions message with requested types in subscriptions element.
1113  * If there wasn't found any match, element subscriptions will be empty.
1114  * If user is not logged in, method returns empty string
1115  */
1116  private String getQueriedSubscriptions(RequestInfo requestInfo){
1117  String retQStr = "";
1118 
1119  // There is no request for list of subscriptions
1120  if(requestInfo.getSubscriptionQuery().isEmpty()){
1121  return retQStr;
1122  }
1123 
1124  // If user is not logged in, subscriptions cannot be even requested
1125  User user = requestInfo.getSession().getUser();
1126  if(user == null){
1127  return retQStr;
1128  }
1129 
1130  Iterator<String> subIt = requestInfo.getSubscriptionQuery().iterator();
1131  ArrayList<String> parsedIds = new ArrayList<String>(P2Constants.SUBS_QUERY_COUNT);
1132 
1133  // Filling up parsedIds array with individual id-s
1134  while(subIt.hasNext()){
1135  String tmpStr = subIt.next();
1136  if(!tmpStr.isEmpty()){
1137  tmpStr = tmpStr.substring(tmpStr.lastIndexOf('/')+1);
1138  tmpStr = tmpStr.replaceAll("\\s+","");
1139  }
1140  else
1141  tmpStr = "%";
1142  parsedIds.add(tmpStr);
1143  }
1144 
1145  retQStr = getQueSubsLike(parsedIds);
1146 
1147  return "<subscriptions>" + retQStr + "</subscriptions>";
1148  }
1149 
1150  /**
1151  * Gets subscriptions from dababase based upon terms in arrayList
1152  * Character % is used as wildcard.
1153  *
1154  * @param qString ArrayList of strings filled with patterns
1155  * @return Returns either subscriptions serialized in XML if there was
1156  * a pattern match or empty string otherwise
1157  */
1158  private String getQueSubsLike(ArrayList<String> qString) {
1159 
1160  String retQStr = "";
1161  List sList = null;
1162  String qStr = qString.get(P2Constants.SUBS_QUERY_URI);
1163 
1164  EntityManager em = AppBean.getPersistenceManager().getEM();
1165  try {
1166  Query query = em.createQuery("SELECT s FROM Subscription s WHERE " +
1167  "s.id LIKE '" + qString.get(P2Constants.SUBS_QUERY_URI) + "' AND "+
1168  "s.userId LIKE '" + qString.get(P2Constants.SUBS_QUERY_AUTHOR) + "' AND " +
1169  "s.groupId LIKE '" + qString.get(P2Constants.SUBS_QUERY_GROUP) + "'");
1170  query.setHint("javax.persistence.cache.storeMode", "REFRESH");
1171  sList = query.getResultList();
1172  } catch (Exception e) {
1174  String msg = "Exception in database query in getQueriedTypeLike.";
1175  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg, e);
1176  }
1177  sList = null;
1178  } finally {
1179  em.close();
1180  }
1181 
1182  if (sList != null && !sList.isEmpty()) {
1183  Iterator sListIt = sList.iterator();
1184  while (sListIt.hasNext()) {
1185  Subscription sub = (Subscription) sListIt.next();
1186  retQStr = retQStr + sub.toXMLStringV2();
1187  }
1188  }
1189  return retQStr;
1190  }
1191 
1192  /**
1193  * Creates message with types of annotations requested (queried) by client
1194  *
1195  * @param requestInfo Informations about client request
1196  * @return Returns types message with requested types in add element. If no
1197  * types matching supplied filter, element add will be empty. If user is not
1198  * logged in or no types requested, it returns empty string.
1199  */
1200  private String getQueriedTypes(RequestInfo requestInfo) {
1201  String retQTStr = "";
1202  if (requestInfo.getQueryTypes().isEmpty()) { // if no types requested
1203  return retQTStr;
1204  }
1205  User user = requestInfo.getSession().getUser();
1206  if (user == null) { // if user is not logged in
1207  return retQTStr; // no types can be requested
1208  }
1209  Iterator<String> qIter = requestInfo.getQueryTypes().iterator();
1210  while (qIter.hasNext()) { // for each filter string from queryTypes message
1211  String qString = qIter.next();
1212  if (!qString.contains("*") && qString.contains(AppBean.getBaseTypeUri())) {
1213  // if filter contains URI without wildcards, exact matching type will be returned
1214  retQTStr = retQTStr + getQueriedTypeExact(qString, requestInfo);
1215  } else if (qString.contains("*") && qString.contains(AppBean.getBaseTypeUri())) {
1216  // if filter contains URI with wildcards
1217  // replaces wildcards by these required for database query
1218  qString = qString.replace("*", "%");
1219  // get matching types
1220  retQTStr = retQTStr + getQueriedTypeLike(qString, requestInfo);
1221  } else if (!qString.contains("*")) {
1222  // if filter contains linearized name without wildcards
1223  // creates list of names to URI
1224  qString = MessageProcessor.replaceArrows(qString);
1225  if (MessageProcessor.isGroupIncluded(qString)) { // if group number is included
1226  // create URI of type
1227  qString = AppBean.getBaseTypeUri() + qString;
1228  // get exactly matching types
1229  retQTStr = retQTStr + getQueriedTypeExact(qString, requestInfo);
1230  } else { // if group number is not included
1231  Iterator<UserGroup> gIter = user.getGroups().iterator();
1232  while (gIter.hasNext()) { // for each group
1233  UserGroup userGroup = gIter.next();
1234  // create URI of type
1235  String qStringWG = AppBean.getBaseTypeUri() + "g" + userGroup.getId() + "/" + qString;
1236  // add types exactly matching in this group
1237  retQTStr = retQTStr + getQueriedTypeExact(qStringWG, requestInfo);
1238  }
1239  }
1240  } else { // if filter contains linearized name with wildcards
1241  // replaces wildcards by these required for database query
1242  qString = qString.replace("*", "%");
1243  qString = MessageProcessor.replaceArrows(qString);
1244  if (MessageProcessor.isGroupIncluded(qString)) { // if group number is included
1245  // create URI of type
1246  qString = AppBean.getBaseTypeUri() + qString;
1247  // get matching types
1248  retQTStr = retQTStr + getQueriedTypeLike(qString, requestInfo);
1249  } else { // if group number is not included
1250  Iterator<UserGroup> gIter = user.getGroups().iterator();
1251  while (gIter.hasNext()) { // for each group
1252  UserGroup userGroup = gIter.next();
1253  // create URI of type
1254  String qStringWG = AppBean.getBaseTypeUri() + "g" + userGroup.getId() + "/" + qString;
1255  // add types matching in this group
1256  retQTStr = retQTStr + getQueriedTypeLike(qStringWG, requestInfo);
1257  }
1258  }
1259  } // if filter contains linearized name with wildcards
1260  } // // for each filter string from queryTypes message
1261  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1262  retQTStr = "<addTypes>" + retQTStr + "</addTypes>";
1263  }else{
1264  retQTStr = "<types><add>" + retQTStr + "</add></types>"; // create message
1265  }
1266  return retQTStr;
1267  } // getQueriedTypes()
1268 
1269  /**
1270  * Gets type of annotations with given URI serialized in XML
1271  *
1272  * @param qString URI of requested type of annotations
1273  * @param requestInfo Informations about client request
1274  * @return Returns type of annotations with given URI serialized in XML or
1275  * empty string if there are no matching type
1276  */
1277  private String getQueriedTypeExact(String qString, RequestInfo requestInfo) {
1278  boolean withAnc = true; // types will be send with ancestors
1279  if (requestInfo.getSession().getProtocolLOD() < Constants.PROTOCOL_LOD_V1_1) {
1280  withAnc = false; // old client - types will be send without ancestors
1281  }
1282  String retQTStr = "";
1283  // query database for type with given URI
1284  Object[] params = new Object[2];
1285  params[0] = "uri";
1286  params[1] = qString;
1287  List tList = AppBean.getPersistenceManager().queryDB("AnnotType.findByUri", params);
1288  if (tList != null && !tList.isEmpty()) { // if type was found
1289  AnnotType aType = (AnnotType) tList.get(0);
1290  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1291  retQTStr = retQTStr + aType.toXMLStringV2(withAnc);
1292  }else{
1293  retQTStr = retQTStr + aType.toXMLString(withAnc);
1294  }
1295  }
1296  return retQTStr;
1297  } // getQueriedTypeExact()
1298 
1299  /**
1300  * Gets type of annotations with URI matching given pattern serialized in XML
1301  * Character % is used as wildcard.
1302  *
1303  * @param qString Pattern of URI of requested type of annotations
1304  * @param requestInfo Informations about client request
1305  * @return Returns type of annotations with URI matching given pattern
1306  * serialized in XML or empty string if there are no matching type
1307  */
1308  private String getQueriedTypeLike(String qString, RequestInfo requestInfo) {
1309  boolean withAnc = true; // types will be send with ancestors
1310  if (requestInfo.getSession().getProtocolLOD() < Constants.PROTOCOL_LOD_V1_1) {
1311  withAnc = false; // old client - types will be send without ancestors
1312  }
1313  String retQTStr = "";
1314  List tList = null;
1315  // query database for matching types
1316  EntityManager em = AppBean.getPersistenceManager().getEM();
1317  try {
1318  Query query = em.createQuery("SELECT a FROM AnnotType a WHERE a.uri LIKE '" + qString + "'");
1319  query.setHint("javax.persistence.cache.storeMode", "REFRESH");
1320  tList = query.getResultList();
1321  } catch (Exception e) {
1323  String msg = "Exception in database query in getQueriedTypeLike.";
1324  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg, e);
1325  }
1326  tList = null;
1327  } finally {
1328  em.close();
1329  }
1330 
1331  if (tList != null && !tList.isEmpty()) { // if some types was found
1332  Iterator tListIt = tList.iterator();
1333  while (tListIt.hasNext()) { // serialize types
1334  AnnotType aType = (AnnotType) tListIt.next();
1335  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1336  retQTStr = retQTStr + aType.toXMLStringV2(withAnc);
1337  }else{
1338  retQTStr = retQTStr + aType.toXMLString(withAnc);
1339  }
1340  }
1341  }
1342  return retQTStr;
1343  } // getQueriedTypeLike()
1344 
1345  /**
1346  * Creates message with annotations interesting for client Annotations are
1347  * returned in element add of annotations message.
1348  *
1349  * @param requestInfo Informations about client request
1350  * @return Returns message with annotations interesting for client or empty
1351  * string if there are no such annotations.
1352  */
1353  private String getAllInterestingAnnotations(RequestInfo requestInfo) {
1354  String retStr = "";
1355  boolean proto11 = requestInfo.getSession().getProtocolLOD() > 1;
1356  ArrayList<Annotation> approvedAnnotations = new ArrayList<Annotation>();
1357  ArrayList<Annotation> unapprovedAnnotations = new ArrayList<Annotation>();
1358  // query database for annotations for synchronized document
1359  Object[] params = new Object[2];
1360  params[0] = "sourceDocumentId";
1361  params[1] = requestInfo.getSession().getSyncDocument().getId();
1362  List aList = AppBean.getPersistenceManager().queryDB("Annotation.findBySourceDocumentIDNoNested", params);
1363  if (aList != null && !aList.isEmpty()) { // if some annotation was found
1364  Iterator aListIt = aList.iterator();
1365  while (aListIt.hasNext()) { // for each annotation for given document
1366  Annotation annot = (Annotation) aListIt.next();
1367  //Debug try catch construction
1368  try{
1369  if (requestInfo.getSession().isSubscribed(annot, getUserSources(annot.getUser()))) { // only subscribed annotations
1370  approvedAnnotations.add(annot); // add approved anotation to approved list
1371  } else {
1372  unapprovedAnnotations.add(annot); // other annotations add to unapproved list
1373  }
1374  }
1375  catch(Exception e){
1376  int langNum = requestInfo.getSession().getLanguageNum();
1377  int lod = requestInfo.getSession().getProtocolLOD();
1378  requestInfo.addError(lod, langNum, Localisation.ERROR_100_UNKNOWN_ERROR, e.getMessage());
1379  }
1380  }
1381 
1382  approvedAnnotations = getRelatedAnnotations(approvedAnnotations, unapprovedAnnotations, requestInfo.getSession());
1383 
1384  ArrayList<String> attFilter = requestInfo.getSession().getAttFilter();
1385  int lNum = requestInfo.getSession().getLanguageNum();
1386  Boolean KBRefMode = requestInfo.getSession().getKBRefMode();
1387 
1388  Iterator<Annotation> approvedIter = approvedAnnotations.iterator();
1389 
1390  while (approvedIter.hasNext()) { // for each approved annotation
1391  Annotation annot = approvedIter.next();
1392  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1393  retStr = retStr + annot.toXMLStringV2(attFilter, lNum, KBRefMode);
1394  }else{
1395  retStr = retStr + annot.toXMLString(proto11);
1396  }
1397  }
1398  } // if some annotation was found
1399  if (!retStr.isEmpty()) { // if there are any annotation to send
1400  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1401  retStr = "<addAnnotations>" + retStr + "</addAnnotations>";
1402  }else{
1403  retStr = "<annotations><add>" + retStr + "</add></annotations>"; // create message
1404  }
1405  }
1406  return retStr;
1407  } // getAllInterestingAnnotations()
1408 
1409  /**
1410  * Gets provider of matchers for finding fragments
1411  *
1412  * @return Returns provider of matchers for finding fragments
1413  */
1415  return matcherProvider;
1416  }
1417 
1418  /**
1419  * Updates all annotations for synchronized document (eg. after modification
1420  * of document)
1421  *
1422  * @param requestInfo Informations about client request
1423  */
1424  private void updateAllExisting(RequestInfo requestInfo) {
1425  if (requestInfo.getSession().getSyncDocument() == null) {
1426  // if no document synchronized, no annotations can be updated
1427  return;
1428  }
1429  // query database for all annotations for synchronized document
1430  Flier flier = requestInfo.getFlier();
1431  Object[] params = new Object[2];
1432  params[0] = "sourceDocumentId";
1433  params[1] = requestInfo.getSession().getSyncDocument().getId();
1434  List aList = AppBean.getPersistenceManager().queryDB("Annotation.findBySourceDocumentID", params);
1435  if (aList != null && !aList.isEmpty()) { // if there are annotations for given document
1436  Iterator aListIt = aList.iterator();
1437  while (aListIt.hasNext()) { // for each annotation
1438  Annotation annot = (Annotation) aListIt.next();
1439  // update annotation
1440  if (updateAnnotation(annot, requestInfo)) {
1441  // if annotation was updated, add it to flier (informations for clients)
1442  flier.addAutoUpdatedAnnotation(annot);
1443  }
1444  }
1445  } // if there are annotations for given document
1446  } // updateAllExisting()
1447 
1448  /**
1449  * Updates fragments in annotation
1450  *
1451  * @param annotation Annotation to update
1452  * @param requestInfo Informations about client request
1453  * @return If annotation was updated, returns true, if no update needed or
1454  * error occurred, returns false
1455  */
1456  private boolean updateAnnotation(Annotation annotation, RequestInfo requestInfo) {
1457  if (annotation.getFragments().size() < 1) {
1458  // if annotation haven't fragments, it can't be updated
1459  return false;
1460  }
1461 
1462  // get synchronized document
1463  AnnotDocument annotDoc = requestInfo.getSession().getSyncDocument();
1464  Document parsedDoc = requestInfo.getSession().getParsedSyncDocument();
1465  if (parsedDoc == null) {
1466  int langNum = requestInfo.getSession().getLanguageNum();
1467  int lod = requestInfo.getSession().getProtocolLOD();
1468  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
1469  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
1470  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
1472  String msg = "Bad document for update of the annotation.";
1473  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
1474  }
1475  return false; // updating is not possible
1476  }
1477 
1478  int badFragCnt = 0; // total count of bad fragments
1479  int nowOrphFragCnt = 0; // count of just orphaned fragments
1480  int updatedFragCnt = 0; // count of just updated fragments
1481  Iterator<Fragment> fragIt = annotation.getFragments().iterator();
1482  while (fragIt.hasNext()) { // for each annotated fragment
1483  Fragment fragment = fragIt.next();
1484  try {
1485  // find fragment
1486  UpdatableFragment uf = exactMatcherProvider.match(parsedDoc, fragment.toUpdatableFragment());
1487  if (uf != null) { // if fragment was found
1488  if (fragment.getIsGood() == false) { // if fragment was marked as bad
1489  fragment.setIsGood(true); // mark as good
1490  updatedFragCnt++; // fragment was now updated
1491  }
1492  } else { // if fragment was not found
1493  // find fragment in document with full matching capability
1494  //ArrayList <UpdatableFragment> ufl = matcherProvider.matchAllIncrementally(parsedDoc, fragment.toUpdatableFragment());
1495  ArrayList<UpdatableFragment> ufl;
1496  // find nearest fragment
1497  ufl = nearestMatcherProvider.matchIn3ClosestNodes(parsedDoc, fragment.toUpdatableFragment());
1498  if (ufl.isEmpty()) {
1499  // find fragment with sequence iterator
1500  ufl = sequenceMatcherProvider.matchAllIncrementally(parsedDoc, fragment.toUpdatableFragment());
1501  }
1502  if (ufl.size() == 1) { // if one fragment was found
1503  // get fragment (index unknown)
1504  Iterator<UpdatableFragment> ufIt = ufl.iterator();
1505  uf = ufIt.next();
1506  fragment.updateWithUpdatableFragment(uf); // update fragment in annotation
1507  updatedFragCnt++;
1508  if (fragment.getIsGood() == false) { // if fragment was marked as bad
1509  fragment.setIsGood(true); // mark as good
1510  }
1511  } else if (ufl.isEmpty()) { // if no fragment found
1512  if (fragment.getIsGood() == true) { // if fragment was not bad
1513  fragment.setIsGood(false); // mark as bad
1514  nowOrphFragCnt++; // fragment was now orphaned
1515  }
1516  badFragCnt++;
1517  } else { // if searching is ambiguous
1518  // select the best fragment
1519  int minBadness = Integer.MAX_VALUE; // minimal badness
1520  UpdatableFragment minBFR = null; // fragment with minimal L.D.
1521  String origPath = fragment.getPath(); // original XPath
1522  String origText = fragment.getAnnotatedText(); // original annotated text
1523  int origOffset = fragment.getOffset(); // original offset
1524  for (Iterator<UpdatableFragment> uflIt = ufl.iterator(); uflIt.hasNext();) {
1525  UpdatableFragment ufr = uflIt.next();
1526  int lD = Util.levenshtein(origPath, ufr.getXPathString());
1527  int lDT = Util.levenshtein(origText, ufr.getText());
1528  double LTPerc = (lDT / (double)origText.length()) * 100;
1529  int offDistance = Math.abs(origOffset - ufr.getOffset());
1530  int badness = 355 * lD + 5 * (int)LTPerc + offDistance;
1531  if (badness < minBadness) {
1532  minBadness = badness;
1533  minBFR = ufr;
1534  }
1535  }
1536  uf = minBFR;
1537  fragment.updateWithUpdatableFragment(uf); // update fragment in annotation
1538  updatedFragCnt++;
1539  if (fragment.getIsGood() == false) { // if fragment was marked as bad
1540  fragment.setIsGood(true); // mark as good
1541  }
1542  } // if searching is ambiguous
1543  } // if fragment was not found
1544  } catch (XPathExpressionException ex) { // bad XPath in fragment
1545  if (fragment.getIsGood() == true) { // if fragment was not bad
1546  fragment.setIsGood(false); // mark as bad
1547  nowOrphFragCnt++; // fragment was now orphaned
1548  }
1549  badFragCnt++;
1550  } catch (Exception e) {
1551  int langNum = requestInfo.getSession().getLanguageNum();
1552  int lod = requestInfo.getSession().getProtocolLOD();
1553  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
1554  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
1556  String msg = "Exception during the update of the annotation.";
1557  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg, e);
1558  }
1559  badFragCnt++;
1560  break;
1561  }
1562  } // for each annotated fragment
1563 
1564  if (badFragCnt == annotation.getFragments().size() && nowOrphFragCnt > 0) {
1565  // if annotation was just orphaned
1566  int langNum = requestInfo.getSession().getLanguageNum();
1567  int lod = requestInfo.getSession().getProtocolLOD();
1568  if (lod < Constants.PROTOCOL_LOD_V2) {
1569  String info = AppBean.getBaseAnnotUri() + annotation.getId();
1570  requestInfo.addWarning(lod, langNum, Localisation.WARNING_2_ANNOT_ORPHANED, info);
1571  } else {
1572  String info = annotation.getURIV2();
1573  requestInfo.addWarning(lod, langNum, Localisation.WARNING_2_ANNOT_ORPHANED, info);
1574  }
1575  } else if (nowOrphFragCnt > 0) {
1576  // if annotation was just partially orphaned (bad fragments found)
1577  int langNum = requestInfo.getSession().getLanguageNum();
1578  int lod = requestInfo.getSession().getProtocolLOD();
1579  if (lod < Constants.PROTOCOL_LOD_V2) {
1580  String info = AppBean.getBaseAnnotUri() + annotation.getId();
1581  requestInfo.addWarning(lod, langNum, Localisation.WARNING_4_ANNOT_PART_ORPHANED, info);
1582  } else {
1583  String info = annotation.getURIV2();
1584  requestInfo.addWarning(lod, langNum, Localisation.WARNING_4_ANNOT_PART_ORPHANED, info);
1585  }
1586  }
1587  if (nowOrphFragCnt > 0 || updatedFragCnt > 0) {
1588  // if new bad fragments found or some fragments was updated
1589  return true;
1590  }
1591  return false; // nothing updated (no update needed)
1592  } // updateAnnotation()
1593 
1594  /**
1595  * Checks whether added and changed annotations have good fragments and
1596  * required attributes
1597  *
1598  * @param requestInfo Informations about client request
1599  */
1600  private void checkAddedAndEdited(RequestInfo requestInfo) {
1601  Flier flier = requestInfo.getFlier();
1602  String info;
1603  // get synchronized document
1604  AnnotDocument syncDoc = requestInfo.getSession().getSyncDocument();
1605  // check fragments in added annotations
1606  Iterator<Annotation> addedIter = flier.getAddedAnnotations().iterator();
1607  while (addedIter.hasNext()) { // for each added annotation
1608  Annotation annot = addedIter.next();
1609  // Update all AnnotationLink attributes in added annotations
1610  updateAnnotationLinks(annot, requestInfo);
1611  Boolean cmpErr = false;
1612  Integer updFr = 0;
1613  // check annotation and get number of bad fragments
1614  int badCnt = checkAnnotationFragments(annot, syncDoc, requestInfo, cmpErr, updFr);
1615  int lod = requestInfo.getSession().getProtocolLOD();
1616  if (lod >= Constants.PROTOCOL_LOD_V2 && badCnt > 0) {
1617  addedIter.remove(); // remove bad annotation - in protocol 2.0 it can not be saved
1618  continue;
1619  }
1620  if (badCnt > 0 && annot.getFragments().isEmpty()) { // if annotation was orphaned
1621  int langNum = requestInfo.getSession().getLanguageNum();
1622  if (lod < Constants.PROTOCOL_LOD_V2) {
1623  requestInfo.addWarning(lod, langNum, Localisation.WARNING_2_ANNOT_ORPHANED, annot.getURI());
1624  } else {
1625  requestInfo.addWarning(lod, langNum, Localisation.WARNING_2_ANNOT_ORPHANED, annot.getURIV2());
1626  }
1627 
1628  }
1629  if ((cmpErr || updFr > 0) && !requestInfo.getErrorList().contains(Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC)) {
1630  // If there are errors of searching fragments, it can be synchronization error
1631  // that´s why it must be treated as synchronization error.
1632  // This error will be sent only once.
1633  // Protocol V2 is used
1634  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1635  info = "<resynchronize resource=\"" + requestInfo.getSession().getSyncDocument().getUri()
1636  + "\" method=\"soft\"/>";
1637  }
1638  // Protocol 1.x is used
1639  else{
1640  info = "<resynchronize/>";
1641  }
1642  int langNum = requestInfo.getSession().getLanguageNum();
1643  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC, info);
1644  requestInfo.appendToReply(info);
1646  String msg = "Synchronization error during the added annotation check.";
1647  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
1648  }
1649  }
1650  if (updFr > 0) { // if there are updated fragments
1651  // create warning message
1652  int langNum = requestInfo.getSession().getLanguageNum();
1653  if (lod < Constants.PROTOCOL_LOD_V2) {
1654  requestInfo.addWarning(lod, langNum, Localisation.WARNING_6_FRAGMENTS_UPDATED);
1655  } else {
1656  requestInfo.addWarning(lod, langNum, Localisation.WARNING_6_FRAGMENTS_UPDATED, annot.getURIV2());
1657  }
1658 
1659  }
1660  } // for each added annotation
1661  // check fragments in changed annotations
1662  Iterator<Annotation> editedIter = flier.getEditedAnnotations().iterator();
1663  while (editedIter.hasNext()) { // for each changed annotation
1664  Annotation annot = editedIter.next();
1665  // Update all AnnotationLink attributes in edited annotations
1666  updateAnnotationLinks(annot, requestInfo);
1667  Boolean cmpErr = false;
1668  Integer updFr = 0;
1669  // check annotation and get number of bad fragments
1670  int badCnt = checkAnnotationFragments(annot, syncDoc, requestInfo, cmpErr, updFr);
1671  if (badCnt > 0) { // if there are bad fragments
1672  // create error message
1673  int langNum = requestInfo.getSession().getLanguageNum();
1674  int lod = requestInfo.getSession().getProtocolLOD();
1675  if (lod < Constants.PROTOCOL_LOD_V2) {
1676  info = "<reload uri=\"" + annot.getURI() + "\"/>";
1677  requestInfo.addError(lod, langNum, Localisation.ERROR_38_ANNOT_MALFORMED, info);
1678  } else {
1679  info = "<annotation uri=\"" + annot.getURIV2() + "\"/>";
1680  requestInfo.addError(lod, langNum, Localisation.ERROR_25_BAD_FRAGMENT, info);
1681  }
1683  String msg = "Edited annotation was not saved due bad fragments.";
1684  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
1685  }
1686  // remove annotation from changed annotations (changes can't be saved)
1687  editedIter.remove();
1688  if (cmpErr && !requestInfo.getErrorList().contains(Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC)) {
1689  // If there are errors of searching fragments, it can be synchronization error
1690  // that´s why it must be treated as synchronization error.
1691  // This error will be sent only once.
1692  // Protocol V2 is used
1693  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1694  info = "<resynchronize resource=\"" + requestInfo.getSession().getSyncDocument().getUri()
1695  + "\" method=\"soft\"/>";
1696  }
1697  // Protocol 1.x is used
1698  else{
1699  info = "<resynchronize/>";
1700  }
1701  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC, info);
1702  requestInfo.appendToReply(info);
1703  }
1704  } else if (updFr > 0) { // if there are updated fragments, but no bad fragments
1705  // create warning message
1706  int langNum = requestInfo.getSession().getLanguageNum();
1707  int lod = requestInfo.getSession().getProtocolLOD();
1708  if (lod < Constants.PROTOCOL_LOD_V2) {
1709  requestInfo.addWarning(lod, langNum, Localisation.WARNING_6_FRAGMENTS_UPDATED, annot.getURI());
1710  } else {
1711  requestInfo.addWarning(lod, langNum, Localisation.WARNING_6_FRAGMENTS_UPDATED, annot.getURIV2());
1712  }
1713  if (!requestInfo.getErrorList().contains(Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC)) {
1714  // probably synchronization error (will be sent only once)
1715  // Protocol V2 is used
1716  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
1717  info = "<resynchronize resource=\"" + requestInfo.getSession().getSyncDocument().getUri()
1718  + "\" method=\"soft\"/>";
1719  }
1720  // Protocol 1.x is used
1721  else{
1722  info = "<resynchronize/>";
1723  }
1724  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC, info);
1725  requestInfo.appendToReply(info);
1727  String msg = "Synchronization error during the edited annotation check.";
1728  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
1729  }
1730  }
1731  }
1732  } // for each changed annotation
1733 
1734  // check required attributes in added annotations
1735  addedIter = flier.getAddedAnnotations().listIterator();
1736  while (addedIter.hasNext()) { // for each added annotation
1737  Annotation annot = addedIter.next();
1738  if (checkReqAttrsInAnnot(annot, requestInfo)) { // if attributes aren't presented or filled
1739  addedIter.remove(); // remove annotation (annotation can't be saved)
1740  }
1741  }
1742  // check required attributes in changed annotations
1743  editedIter = flier.getEditedAnnotations().iterator();
1744  while (editedIter.hasNext()) { // for each changed annotation
1745  Annotation annot = editedIter.next();
1746  if (checkReqAttrsInAnnot(annot, requestInfo)) { // if attributes aren't presented or filled
1747  editedIter.remove(); // remove annotation (changes can't be saved)
1748  }
1749  }
1750  } // checkAddedAndEdited()
1751 
1752  /**
1753  * Checks whether annotation have all required attributes filled
1754  *
1755  * @param annot Annotation to check
1756  * @param requestInfo Informations about client request
1757  * @return If annotation have all required attributes filled, returns true,
1758  * false otherwise
1759  */
1760  private boolean checkReqAttrsInAnnot(Annotation annot, RequestInfo requestInfo) {
1761  if (annot.getAnnotType() == null) { // if annotation haven't valid type
1762  return false; // required attributes can't be checked and annotation is bad
1763  }
1764  // get attributes of annotation
1765  Iterator<BaseAttribute> annotAttrIt = annot.getAttributes().iterator();
1766  ArrayList<String> names = new ArrayList<String>(); // names of filled attributes
1767  while (annotAttrIt.hasNext()) { // for each attribute
1768  BaseAttribute attr = annotAttrIt.next();
1769  if (!attr.isEmpty()) { // if attribute has value (not empty)
1770  names.add(attr.getName()); // add name of attribute
1771  }
1772  }
1773  StringBuilder missing = new StringBuilder();
1774  StringBuilder missing2 = new StringBuilder();
1775  AnnotType annotType = annot.getAnnotType();
1776 
1777  //Check if annotation type, is in modified types
1778  Iterator<AnnotType> modifiedIt = requestInfo.getFlier().getEditedTypes().iterator();
1779  while(modifiedIt.hasNext()){
1780  AnnotType tmp = modifiedIt.next();
1781  if(tmp.getId() == annotType.getId()){
1782  annotType = tmp;
1783  break;
1784  }
1785  }
1786 
1787  Iterator<AnnotTypeAttr> attrIt = annotType.getAttributes().iterator();
1788  while (attrIt.hasNext()) { // for each attribute of given type of annotation
1789  AnnotTypeAttr attr = attrIt.next();
1790  if (attr.getRequired()) { // if attribute is required
1791  if (!names.contains(attr.getName())) { // if attribute is not in filled attributes
1792  missing.append(attr.toXMLString(false)); // add to list of missing attributes
1793  missing2.append("<attribute annotationUri=\"").append(annot.getURIV2());
1794  missing2.append("\" uri=\"").append(attr.getUriInOntology());
1795  missing2.append("\" name=\"").append(attr.getName());
1796  missing2.append("\" valueType=\"").append(attr.getValueType());
1797  missing2.append("\" type=\"").append(attr.getTypeUriV2()).append("\"/>");
1798  }
1799  }
1800  }
1801  int lod = requestInfo.getSession().getProtocolLOD();
1802  int langNum = requestInfo.getSession().getLanguageNum();
1803  if (missing2.length() > 0 && lod >= Constants.PROTOCOL_LOD_V2) {
1804  requestInfo.addError(lod, langNum, Localisation.ERROR_6_ATTRIBUTE_REQUIRED, missing2.toString());
1805  return true; // annotation haven't all attributes filled
1806  } else if (missing.length() > 0) { // if there are missing attributes
1807  // create error message
1808  String info = "";
1809  if (annot.getId() != null) {
1810  info = "<reload uri=\"" + annot.getId() + "\"/>";
1811  }
1812  info = info + missing.toString();
1813  requestInfo.addError(lod, langNum, Localisation.ERROR_6_ATTRIBUTE_REQUIRED, info);
1814  return true; // annotation haven't all attributes filled
1815  }
1816  return false; // annotation have all required attributes filled
1817  } // checkReqAttrsInAnnot()
1818 
1819  /**
1820  * Check whether annotated fragments in given annotation are good (can be
1821  * found in synchronized document)
1822  *
1823  * @param annotation Annotation to check
1824  * @param annotDoc Synchronized document
1825  * @param requestInfo Informations about client request
1826  * @param cmpErr In this object this method returns, whether there are errors
1827  * of searching fragments (maybe synchronization error)
1828  * @param updFr In this object this method returns count of updated fragments
1829  * @return Returns number of bad fragments
1830  */
1831  private int checkAnnotationFragments(Annotation annotation, AnnotDocument annotDoc, RequestInfo requestInfo, Boolean cmpErr, Integer updFr) {
1832  int badFragCnt = 0;
1833  Document syncDoc = requestInfo.getSession().getParsedSyncDocument();
1834  if (syncDoc == null) {
1835  int langNum = requestInfo.getSession().getLanguageNum();
1836  int lod = requestInfo.getSession().getProtocolLOD();
1837  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
1838  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
1839  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
1841  String msg = "Bad document for check of the annotation fragments.";
1842  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
1843  }
1844  return 1; // there are bad fragments (probably all, but suffices report one)
1845  // If this is new annotation, all fragments will be saved, but they will
1846  // be checked and updated or marked as bad in actualization further.
1847  }
1848 
1849  ListIterator<Fragment> fragIt = annotation.getFragments().listIterator();
1850  while (fragIt.hasNext()) { // for each fragment
1851  Fragment fragment = fragIt.next();
1852  try {
1853  // find fragment in document
1854  ArrayList<UpdatableFragment> ufl = exactMatcherProvider.matchAll(syncDoc, fragment.toUpdatableFragment());
1855  if (ufl.isEmpty() && fragment.getPath() != null && fragment.getPath().indexOf("text()") == -1) {
1856  // if "/text()" is not presented, we can try append it
1857  Fragment fwt = new Fragment(fragment.getPath(), fragment.getOffset(), fragment.getLength(), fragment.getAnnotatedText(), fragment.getRefAnnotation(), fragment.getIsGood());
1858  fwt.setPath(fragment.getPath() + "/text()");
1859  ufl = exactMatcherProvider.matchAll(syncDoc, fwt.toUpdatableFragment());
1860  }
1861  UpdatableFragment uf = null;
1862  if (ufl.size() > 1) { // if there is more then one matching fragment
1863  // In some cases, one fragment can be selected and its exact XPath used
1864  // It's necessary to try:
1865 
1866  // - remove fragments with different string
1867  ListIterator<UpdatableFragment> uflIt = ufl.listIterator();
1868  while (uflIt.hasNext()) {
1869  UpdatableFragment inspectedUf = uflIt.next();
1870  if (!fragment.getAnnotatedText().equals(inspectedUf.getText())) {
1871  uflIt.remove();
1872  }
1873  }
1874 
1875  // - remove duplicit fragments
1876  if (ufl.size() > 1) {
1877  uflIt = ufl.listIterator();
1878  while (uflIt.hasNext()) {
1879  UpdatableFragment inspectedUf = uflIt.next();
1880  ListIterator<UpdatableFragment> uflItDup = ufl.listIterator();
1881  while (uflItDup.hasNext()) {
1882  UpdatableFragment ufInList = uflIt.next();
1883  if (ufInList.equals(inspectedUf)) {
1884  uflItDup.remove();
1885  }
1886  }
1887  }
1888  }
1889 
1890  // evaluate correction process
1891  if (ufl.size() == 1) {
1892  uf = ufl.get(0);
1893  } else {
1894  // create error message
1895  int langNum = requestInfo.getSession().getLanguageNum();
1896  int lod = requestInfo.getSession().getProtocolLOD();
1897  String info = "";
1898  if (annotation.getId() != null && lod < Constants.PROTOCOL_LOD_V2) {
1899  info = "<reload uri=\"" + annotation.getURI() + "\"/>" + fragment.toXMLString();
1900  } else if (annotation.getId() != null) {
1901  info = "<annotation uri=\"" + annotation.getURIV2() + "\"/>";
1902  }
1903  requestInfo.addError(lod, langNum, Localisation.ERROR_52_AMBIGUOUS_FRAGMENT, info);
1905  String msg = "Ambiguous fragment found.";
1906  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
1907  }
1908  // remove fragment from annotation
1909  fragIt.remove();
1910  badFragCnt++;
1911  continue;
1912  }
1913  } else if (!ufl.isEmpty()) { // if fragment was found
1914  // get fragment (index unknown)
1915  Iterator<UpdatableFragment> ufIt = ufl.iterator();
1916  uf = ufIt.next();
1917  } else { // if fragment was not found
1918  // find nearest fragment
1919  ufl = nearestMatcherProvider.matchIn3ClosestNodes(syncDoc, fragment.toUpdatableFragment());
1920  if (ufl.isEmpty()) {
1921  // find fragment with sequence iterator
1922  ufl = sequenceMatcherProvider.matchAllIncrementally(syncDoc, fragment.toUpdatableFragment());
1923  }
1924  // find fragment in document with full matching capability
1925  //ufl = matcherProvider.matchAllIncrementally(syncDoc, fragment.toUpdatableFragment());
1926  if (ufl.size() == 1) {
1927  // get fragment (index unknown)
1928  Iterator<UpdatableFragment> ufIt = ufl.iterator();
1929  uf = ufIt.next();
1930  } else if (ufl.isEmpty()) { // if fragment still was not found
1931  // create error message
1932  int langNum = requestInfo.getSession().getLanguageNum();
1933  int lod = requestInfo.getSession().getProtocolLOD();
1934  String info = "";
1935  if (annotation.getId() != null && lod < Constants.PROTOCOL_LOD_V2) {
1936  info = "<reload uri=\"" + annotation.getId() + "\"/>" + fragment.toXMLString();
1937  } else if (annotation.getId() != null) {
1938  info = "<annotation uri=\"" + annotation.getURIV2() + "\"/>";
1939  }
1940  requestInfo.addError(lod, langNum, Localisation.ERROR_25_BAD_FRAGMENT, info);
1942  String msg = "Bad fragment found during the check.";
1943  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
1944  }
1945  cmpErr = true; // error of comparation with document content
1946  badFragCnt++; // bad fragment found
1947  fragIt.remove(); // remove fragment from annotation
1948  } else { // if searching is ambiguous
1949  // try select proper fragment
1950  int pathLenght = fragment.getPath().length();
1951  int textLenght = fragment.getAnnotatedText().length();
1952  if (pathLenght > Constants.MIN_XPATH_L_TO_AUTO_MODIFY_FR
1953  && textLenght > Constants.MIN_TEXT_L_TO_AUTO_MODIFY_FR) { // if lengths are sufficient
1954  int minLD = Integer.MAX_VALUE; // minimal Levenshtein distance
1955  UpdatableFragment minLDFR = null; // fragment with minimal L.D.
1956  int sMinLD = Integer.MAX_VALUE; // second minimal Levenshtein distance
1957  double minLDPercentage = 1;
1958  double sMinLDPercentage = 1;
1959  String origPath = fragment.getPath(); // original XPath
1960  String origText = fragment.getAnnotatedText(); // original text
1961  for (Iterator<UpdatableFragment> uflIt = ufl.iterator(); uflIt.hasNext();) {
1962  UpdatableFragment ufr = uflIt.next();
1963  if (Math.abs(ufr.getOffset() - fragment.getOffset()) > Constants.MAX_OFFSET_DIFF_TO_AUTO_MODIFY_FR) {
1964  continue; // bad fragment
1965  }
1966  if (((double) Util.levenshtein(origText, ufr.getText()) / origText.length()) > Constants.MAX_TEXT_LD_TO_AUTO_MODIFY_FR) {
1967  continue; // bad fragment
1968  }
1969  int lD = Util.levenshtein(origPath, ufr.getXPathString());
1970  if (lD < minLD) {
1971  sMinLD = minLD;
1972  minLD = lD;
1973  minLDPercentage = (double) lD / pathLenght;
1974  minLDFR = ufr;
1975  } else if (lD < sMinLD) {
1976  sMinLD = lD;
1977  sMinLDPercentage = (double) lD / pathLenght;
1978  }
1979  }
1980  if (minLDPercentage <= Constants.MAX_XPATH_LD_TO_AUTO_MODIFY_FR
1981  && (sMinLDPercentage - minLDPercentage) >= Constants.MIN_XPATH_LD_DIFF_TO_AUTO_MODIFY_FR) {
1982  uf = minLDFR; // similar fragment will be used
1983  // create warning message
1984  int langNum = requestInfo.getSession().getLanguageNum();
1985  int lod = requestInfo.getSession().getProtocolLOD();
1986  String info = "";
1987  if (annotation.getId() != null && lod < Constants.PROTOCOL_LOD_V2) {
1988  info = annotation.getURI();
1989  } else if (lod >= Constants.PROTOCOL_LOD_V2) {
1990  info = annotation.getURIV2();
1991  }
1992  requestInfo.addWarning(lod, langNum, Localisation.WARNING_3_ANNOT_UPDATED, info);
1993  }
1994  } // if lengths are sufficient
1995 
1996  if (uf == null) { // if searching is too ambiguous
1997  // create error message
1998  int langNum = requestInfo.getSession().getLanguageNum();
1999  int lod = requestInfo.getSession().getProtocolLOD();
2000  String info = "";
2001  if (annotation.getId() != null && lod < Constants.PROTOCOL_LOD_V2) {
2002  info = "<reload uri=\"" + annotation.getURI() + "\">" + fragment.toXMLString() + "</reload>";
2003  } else if (annotation.getId() != null) {
2004  info = "<annotation uri=\"" + annotation.getURIV2() + "\"/>";
2005  }
2006  requestInfo.addError(lod, langNum, Localisation.ERROR_25_BAD_FRAGMENT, info);
2008  String msg = "Too ambiguous fragment found.";
2009  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2010  }
2011  cmpErr = true; // error ofcomparation with document content
2012  badFragCnt++; // bad fragment found
2013  fragIt.remove(); // remove fragment from annotation
2014  }
2015  } // if searching is ambiguous
2016  } // if fragment was not found
2017  if (uf != null && !fragment.fragmentEqualsWUF(uf)) { // if fragment was found, but modified
2018  cmpErr = true; // error ofcomparation with document content
2019  updFr++; // increase count of updated fragments
2020  fragment.updateWithUpdatableFragment(uf); // update fragment data
2021  }
2022  } catch (XPathExpressionException ex) {
2023  int langNum = requestInfo.getSession().getLanguageNum();
2024  int lod = requestInfo.getSession().getProtocolLOD();
2025  String info = "";
2026  if (lod < Constants.PROTOCOL_LOD_V2) {
2027  if (annotation.getId() != null) {
2028  info = "<reload uri=\"" + annotation.getURI() + "\"/>" + fragment.toXMLString();
2029  }
2030  requestInfo.addError(lod, langNum, Localisation.ERROR_35_BAD_XPATH, info);
2031  } else {
2032  if (annotation.getId() != null) {
2033  info = "<annotation uri=\"" + annotation.getURIV2() + "\"/>" + fragment.toXMLString();
2034  }
2035  requestInfo.addError(lod, langNum, Localisation.ERROR_25_BAD_FRAGMENT, info);
2036  }
2037  fragIt.remove(); // remove fragment from annotation
2039  String msg = "Bad XPath found in the fragment.";
2040  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2041  }
2042  badFragCnt++;
2043  } catch (Exception e) {
2044  int langNum = requestInfo.getSession().getLanguageNum();
2045  int lod = requestInfo.getSession().getProtocolLOD();
2046  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2047  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
2049  String msg = "Exception dugring the checking of the annotation fragments.";
2050  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg, e);
2051  }
2052  badFragCnt++;
2053  }
2054  } // for each fragment
2055  return badFragCnt;
2056  } // checkAnnotationFragments()
2057 
2058  /**
2059  * Checks whether annotations of document will be affected by updating
2060  * annotated copy of document to new version If annotation can be
2061  * automatically updated (all fragments found), it's not treated as affection.
2062  *
2063  * @param doc Old version of annotated document
2064  * @param newDoc New version of annotated document
2065  * @param requestInfo Informations about client request
2066  * @return If annotations are affected, returns true, false otherwise
2067  */
2068  private double isAnnotationsAffected(AnnotDocument doc, AnnotDocument newDoc, RequestInfo requestInfo) {
2069  // query database for annotations for given document
2070  Object[] params = new Object[2];
2071  params[0] = "sourceDocumentId";
2072  params[1] = doc.getId();
2073  List aList = AppBean.getPersistenceManager().queryDB("Annotation.findBySourceDocumentID", params);
2074  if (aList != null && !aList.isEmpty()) { // if annotations was found
2075  int annCount = aList.size();
2076  double incrValue = 100 / annCount;
2077  double affected = 0;
2078  Iterator aListIt = aList.iterator();
2079  while (aListIt.hasNext()) { // for each annotation
2080  Annotation annot = (Annotation) aListIt.next();
2081  affected += incrValue * isAnnotationAffected(annot, newDoc, requestInfo);
2082  }
2083  return affected;
2084  } // if annotations was found
2085  return 0;
2086  } // isAnnotationsAffected()
2087 
2088  /**
2089  * Checks whether annotation will be affected by updating annotated copy of
2090  * document to new version If annotation can be automatically updated (all
2091  * fragments found), it's not treated as affection.
2092  *
2093  * @param annotation Checked annotation
2094  * @param newDoc New version of annotated document
2095  * @param requestInfo Informations about client request
2096  * @return If annotations are affected, returns true, false otherwise
2097  */
2098  private double isAnnotationAffected(Annotation annotation, AnnotDocument newDoc, RequestInfo requestInfo) {
2099  // parse new version of document
2100  Document parsedDoc = null;
2101  try{
2102  parsedDoc = matcherProvider.getDocumentFromString(newDoc.getContent(), false, true);
2103  }
2104  catch(Exception e){
2105  int langNum = requestInfo.getSession().getLanguageNum();
2106  int lod = requestInfo.getSession().getProtocolLOD();
2107  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2108  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
2109  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
2111  String msg = "Parsing document for the annotation affection check failed.";
2112  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2113  }
2114  return 1;
2115  }
2116  //Document parsedDoc = requestInfo.getSession().getParsedSyncDocument();
2117  if (parsedDoc == null) {
2118  int langNum = requestInfo.getSession().getLanguageNum();
2119  int lod = requestInfo.getSession().getProtocolLOD();
2120  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2121  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
2122  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
2124  String msg = "Bad document for the annotation affection check.";
2125  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2126  }
2127  return 1;
2128  }
2129 
2130  Iterator<Fragment> fragIt = annotation.getFragments().iterator();
2131  int fragCount = annotation.getFragments().size();
2132  // Zero fragments
2133  if(fragCount <= 0){
2134  return 0;
2135  }
2136  double incrValue = 1 / fragCount;
2137  double affected = 0;
2138  while (fragIt.hasNext()) { // for each annotated fragment
2139  Fragment fragment = fragIt.next();
2140  try {
2141  // find annotated fragment
2142  UpdatableFragment uf;
2143  uf = exactMatcherProvider.match(parsedDoc, fragment.toUpdatableFragment());
2144  if (uf == null) { // if fragment was not found, annotation will be affected
2145  // find annotated fragment with full matching capability
2146  ArrayList<UpdatableFragment> ufl = nearestMatcherProvider.matchIn3ClosestNodes(parsedDoc, fragment.toUpdatableFragment());
2147  if(ufl.isEmpty()){
2148  ufl = sequenceMatcherProvider.matchAllIncrementally(parsedDoc, fragment.toUpdatableFragment());
2149  }
2150  if(ufl.size() >= 1){
2151  if(ufl.size() == 1){
2152  affected += incrValue * fragmentTreshold(fragment, ufl.get(0));
2153  continue;
2154  }
2155  else{
2156  // select the best fragment
2157  int minBadness = Integer.MAX_VALUE; // minimal badness
2158  UpdatableFragment minBFR = null; // fragment with minimal L.D.
2159  String origPath = fragment.getPath(); // original XPath
2160  String origText = fragment.getAnnotatedText(); // original annotated text
2161  int origOffset = fragment.getOffset(); // original offset
2162  for (Iterator<UpdatableFragment> uflIt = ufl.iterator(); uflIt.hasNext();) {
2163  UpdatableFragment ufr = uflIt.next();
2164  int lD = Util.levenshtein(origPath, ufr.getXPathString());
2165  int lDT = Util.levenshtein(origText, ufr.getText());
2166  double LTPerc = (lDT / (double)origText.length()) * 100;
2167  int offDistance = Math.abs(origOffset - ufr.getOffset());
2168  int badness = 355 * lD + 5 * (int)LTPerc + offDistance;
2169  if (badness < minBadness) {
2170  minBadness = badness;
2171  minBFR = ufr;
2172  }
2173  }
2174  uf = minBFR;
2175  affected += incrValue * fragmentTreshold(fragment, uf);
2176  }
2177  }
2178  else{
2179  if (fragment.getIsGood()) { // if fragment was good
2180  affected += incrValue;
2181  }
2182  }
2183  } // if fragment was not found, annotation will be affected
2184  } catch (XPathExpressionException ex) {
2185  if (fragment.getIsGood()) {
2186  affected += incrValue;
2187  }
2188  } catch (Exception e) {
2189  int langNum = requestInfo.getSession().getLanguageNum();
2190  int lod = requestInfo.getSession().getProtocolLOD();
2191  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2192  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
2194  String msg = "Exception during the annotation affection check.";
2195  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg, e);
2196  }
2197  affected += incrValue;
2198  }
2199  } // for each annotated fragment
2200  return affected;
2201  } // isAnnotationAffected()
2202 
2203  /**
2204  * Treshold method which tells you how much is the fragment changed.
2205  * @param origFragment
2206  * @param foundFragment
2207  * @return fragment affection percentage
2208  */
2209  private double fragmentTreshold(Fragment origFragment, UpdatableFragment foundFragment){
2210  int affectedParts = 0;
2211  // Offset treshold
2212  if(Math.abs(origFragment.getOffset() - foundFragment.getOffset()) > Constants.MAX_OFFSET_DIFF_TO_AUTO_MODIFY_FR){
2213  ++affectedParts;
2214  }
2215  // XPath treshold
2216  int XPathLD = Util.levenshtein(origFragment.getPath(), foundFragment.getXPathString());
2217  double XPathLDPercentage = (double) XPathLD / origFragment.getPath().length();
2218  if(XPathLDPercentage > Constants.MAX_XPATH_LD_TO_AUTO_MODIFY_FR){
2219  ++affectedParts;
2220  }
2221  // Text treshold
2222  int TextLD = Util.levenshtein(origFragment.getAnnotatedText(), foundFragment.getText());
2223  double TextLDPercentage = (double) TextLD / origFragment.getAnnotatedText().length();
2224  if(TextLDPercentage > Constants.MAX_TEXT_LD_TO_AUTO_MODIFY_FR){
2225  ++affectedParts;
2226  }
2227  return (double)affectedParts / 3;
2228  }
2229 
2230  /**
2231  * Synchronizes document
2232  *
2233  * @param requestInfo Informations about client request
2234  */
2235  private void synchronizeDocument(RequestInfo requestInfo) {
2236  // reset data for optimization
2237  requestInfo.getSession().setParsedLinDocument(null);
2238  requestInfo.getSession().setParsedSyncDocument(null);
2239 
2240  // get synchronized document data
2241  AnnotDocument newDoc = requestInfo.getSyncDocumentData();
2242  try {
2243  // Clean up document URI from sessionId and other mess.
2244  newDoc.setUri(cleanUpURI(newDoc.getUri()));
2245  } catch (URISyntaxException ex) { // if URI is bad
2246  // create error messages
2247  int langNum = requestInfo.getSession().getLanguageNum();
2248  int lod = requestInfo.getSession().getProtocolLOD();
2249  requestInfo.addError(lod, langNum, Localisation.ERROR_53_BAD_DOCUMENT_URI);
2251  String msg = "Bad URI of synchronized document: " + newDoc.getUri();
2252  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg, ex);
2253  }
2254  return; // synchronization failed
2255  }
2256 
2257  /* To prevent searching for the same document by multiple threads in the same time, it's needed to use
2258  * locks
2259  */
2260  String documentUri = newDoc.getUri();
2261  ReentrantLock lock;
2262 
2263  /* If lock is null create new lock and put it into map */
2264  synchronized(AppBean.documentURILocker){
2265  /* Get lock from map */
2266  lock = AppBean.documentURILocker.get(documentUri);
2267  if(lock == null){
2268  lock = new ReentrantLock();
2269  }
2270 
2271  AppBean.documentURILocker.put(documentUri, lock);
2272  }
2273  lock.lock();
2274 
2275  // Query database for the annotation document
2276  AnnotDocument doc = null; // synchronized document
2277  Object[] params = new Object[2];
2278  params[0] = "uri";
2279  params[1] = newDoc.getUri();
2280  List dList = AppBean.getPersistenceManager().queryDB("AnnotDocument.findByUri", params);
2281  if (dList != null && !dList.isEmpty()) { // if document was found
2282  doc = (AnnotDocument) dList.get(0);
2283  if(lock.isLocked()){
2284  lock.unlock();
2285  }
2286  }
2287 
2288  if (requestInfo.isSyncLinearized()) { // if linearization is requested
2289  // synchronize with linearization
2290  synchronizeLinearizedDocument(doc, newDoc, requestInfo);
2291  } else {
2292  // synchronize without linearization
2293  synchronizeStructuredDocument(doc, newDoc, requestInfo);
2294  }
2295 
2296  }
2297 
2298  /**
2299  * Locks document usage
2300  *
2301  * @throws InterruptedException
2302  */
2303  public synchronized void lockDocumentUsage() throws InterruptedException{
2304  while(documentUsageLocked){
2305  wait();
2306  }
2307  documentUsageLocked = true;
2308  }
2309 
2310  /**
2311  * Unlocks document usage
2312  */
2313  public synchronized void unlockDocumentUsage() {
2314  documentUsageLocked = false;
2315  notify();
2316  }
2317 
2318  /**
2319  * Synchronizes structured document
2320  *
2321  * @param doc persisted annotated document whether exists, otherwise null
2322  * @param newDoc New version of annotated document
2323  * @param requestInfo Informations about client request
2324  */
2326  EditorSession session = requestInfo.getSession();
2327  int langNum = session.getLanguageNum();
2328  int lod = requestInfo.getSession().getProtocolLOD();
2329 
2330  session.setLinearization(false); // if linearization is enabled, disable it
2331 
2332  Document parsedSyncDocument = null;
2333  try {
2334  parsedSyncDocument = this.matcherProvider.getDocumentFromString(newDoc.getContent(), false, true);
2335  } catch(Exception e){
2336  langNum = requestInfo.getSession().getLanguageNum();
2337  lod = requestInfo.getSession().getProtocolLOD();
2338  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2339  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
2340  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
2342  String msg = "Parsing document to get structured synchronized document failed.";
2343  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2344  }
2345  return; // synchronization failed
2346  }
2347 
2348  try {
2349  try {
2351  } catch (InterruptedException ex) {
2353  String msg = "Interrupted exception on trying to lock document usage counter.";
2354  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2355  }
2356  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2357  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR,info);
2358  return;
2359  }
2360 
2361  session.setSyncDocument(null);
2362  session.setParsedLinDocument(null);
2363  session.setParsedSyncDocument(null);
2364 
2365  if (doc == null) { // if synchronizes new document
2366  // set informations about synchronized document
2367  requestInfo.setNewDocument(true);
2368  requestInfo.setSyncDocument(newDoc);
2369  // set synchronized document in session
2370  session.setSyncDocument(newDoc);
2371  session.setParsedLinDocument(Linearizer.linearizeDocument(parsedSyncDocument));
2372  session.setParsedSyncDocument(parsedSyncDocument);
2373 
2374  } else { // if synchronizes existing document
2375 
2376  // if documents have same content
2377  if (doc.getContent().equals(newDoc.getContent())) {
2378  requestInfo.setSyncDocument(doc); // set informations about synchronized document
2379  // set synchronized document in session
2380  session.setSyncDocument(doc);
2381  session.setParsedLinDocument(Linearizer.linearizeDocument(parsedSyncDocument));
2382  session.setParsedSyncDocument(parsedSyncDocument);
2383  }
2384  // if documents have different content and another client has it opened/synchronized
2385  else if (AppBean.getDocumentUsage(doc.getId()) > 0 && !requestInfo.isSyncWithOverwrite()) {
2386  // synchronization error
2387  StringBuilder docSB = new StringBuilder();
2388  docSB.append("<content><![CDATA[");
2389  docSB.append(doc.getContent());
2390  docSB.append("]]></content>");
2391  requestInfo.addError(lod, langNum, Localisation.ERROR_62_SYNC_ERROR_OTHER_DIFFERENT, docSB.toString());
2393  String msg = "Document is already opened by another client. Versions of document differs.";
2394  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2395  }
2396  }
2397  // if documents have different content, but annotations aren't affected
2398  else if (isAnnotationsAffected(doc, newDoc, requestInfo) <= Constants.MAX_ANNOTATION_DIFFERENCE) {
2399  requestInfo.setSyncDocument(doc); // set informations about synchronized document
2400  // set synchronized document in session
2401  session.setSyncDocument(doc);
2402  session.setParsedLinDocument(Linearizer.linearizeDocument(parsedSyncDocument));
2403  session.setParsedSyncDocument(parsedSyncDocument);
2404  // generate modifications for other clients
2405  generateTextModificationsToFlier(doc.getContent(), newDoc.getContent(), false, requestInfo);
2406  Integer lastModID = AppBean.incrementLastDocModification(doc.getId());
2407  if (lastModID == null) {
2408  String msg = "Unable to increment text modification set ID.";
2410  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2411  }
2412  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, msg);
2413  } else {
2414  requestInfo.getFlier().setTextModificationSetID(lastModID);
2415  AppBean.addDocModificationSet(doc.getId(), lastModID, requestInfo.getFlier().getTextModifications());
2416  }
2417  doc.setModified(new Date()); // set date of modification
2418  doc.setContent(newDoc.getContent()); // actualize content of document
2419  }
2420  // if documents have different content, but overwrite is set
2421  else if (requestInfo.isSyncWithOverwrite()) {
2422  requestInfo.setSyncDocument(doc); // set informations about synchronized document
2423  // set synchronized document in session
2424  session.setSyncDocument(doc);
2425  session.setParsedLinDocument(Linearizer.linearizeDocument(parsedSyncDocument));
2426  session.setParsedSyncDocument(parsedSyncDocument);
2427  // generate modifications for other clients
2428  generateTextModificationsToFlier(doc.getContent(), newDoc.getContent(), false, requestInfo);
2429  Integer lastModID = AppBean.incrementLastDocModification(doc.getId());
2430  if (lastModID == null) {
2431  String msg = "Unable to increment text modification set ID.";
2433  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2434  }
2435  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, msg);
2436  } else {
2437  requestInfo.getFlier().setTextModificationSetID(lastModID);
2438  AppBean.addDocModificationSet(doc.getId(), lastModID, requestInfo.getFlier().getTextModifications());
2439  }
2440  doc.setModified(new Date()); // set date of modification
2441  doc.setContent(newDoc.getContent()); // overwrite content of document
2442  }
2443  // if documents have different content and annotations are affected
2444  else {
2445  // synchronization error
2446  StringBuilder docSB = new StringBuilder();
2447  docSB.append("<content><![CDATA[");
2448  docSB.append(doc.getContent());
2449  docSB.append("]]></content>");
2450  requestInfo.addError(lod, langNum, Localisation.ERROR_9_SYNC_ERROR_DIFFERENT, docSB.toString());
2452  String msg = "Common synchronization error (different contents).";
2453  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2454  }
2455  }
2456  }
2457  } finally {
2459  }
2460 
2461  } // synchronizeDocument()
2462 
2463  /**
2464  * Resynchronizes document
2465  *
2466  * @param requestInfo Informations about client request
2467  */
2468  private void reSynchronizeDocument(RequestInfo requestInfo) {
2469  // reset data for optimization
2470  requestInfo.getSession().setParsedLinDocument(null);
2471  requestInfo.getSession().setParsedSyncDocument(null);
2472 
2473  AnnotDocument doc = requestInfo.getSession().getSyncDocument(); // get synchronized ocument
2474  if (doc == null) { // if document was not synchronized
2475  // error
2476  int langNum = requestInfo.getSession().getLanguageNum();
2477  int lod = requestInfo.getSession().getProtocolLOD();
2478  requestInfo.addError(lod, langNum, Localisation.ERROR_10_SYNC_ERROR_NOT_POSSIBLE);
2480  String msg = "Resynchronization in state where document is not synchronized.";
2481  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2482  }
2483  return;
2484  }
2485 
2486  // get new content of document
2487  String newContent = requestInfo.getResyncDocContent();
2488  // create annotated document with new data
2489  AnnotDocument newDoc = new AnnotDocument(doc.getUri(), newContent);
2490  newDoc.setId(doc.getId());
2491 
2492  if (requestInfo.getSession().isLinearization()) { // if linearization is enabled
2493  reSynchronizeLinearizedDocument(doc, newDoc, requestInfo);
2494  } else {
2495  reSynchronizeStructuredDocument(doc, newDoc, requestInfo);
2496  }
2497  }
2498 
2499  /**
2500  * Resynchronizes document
2501  *
2502  * @param doc Old version of document
2503  * @param newDoc New version of document
2504  * @param requestInfo Informations about client request
2505  */
2507 
2508  if (doc.getContent().equals(newDoc.getContent())) {
2509  // if new content is a same as old content
2510  requestInfo.setSyncDocument(doc); // set informations about synchronization
2511  } else if (isAnnotationsAffected(doc, newDoc, requestInfo) <= Constants.MAX_ANNOTATION_DIFFERENCE) {
2512  // if new content is not a same as old content, but annotations aren't affected
2513  // generate modifications for other clients
2514  generateTextModificationsToFlier(doc.getContent(), newDoc.getContent(), false, requestInfo);
2515  doc.setModified(new Date()); // set date of modification
2516  doc.setContent(newDoc.getContent()); // actualize content of document
2517  requestInfo.setSyncDocument(doc); // set informations about synchronization
2518  } else { // if new content is not a same as old content and annotations are affected
2519  // synchronization error
2520 
2521  int langNum = requestInfo.getSession().getLanguageNum();
2522  int lod = requestInfo.getSession().getProtocolLOD();
2524  String msg = "Common resynchronization error (different contents).";
2525  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2526  }
2527  //If protocol version 2 is used, hard resynchronization should attempt
2528  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
2529  String context = "<content><![CDATA[" + doc.getContent() + "]]></content>";
2530  requestInfo.addError(lod, langNum, Localisation.ERROR_9_SYNC_ERROR_DIFFERENT, context);
2531  }
2532  else{
2533  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC);
2534  }
2535  }
2536  } // reSynchronizeDocument()
2537 
2538  /**
2539  * Returns name of this module. Name will be included in XML element attribute
2540  * so usable characters are restricted.
2541  *
2542  * @param lang Requested language of name
2543  * @return Name of this module
2544  */
2545  @Override
2546  public String getModuleName(int lang) {
2547  return "Core module";
2548  }
2549 
2550  /**
2551  * Returns description of this module. It will be part of information about
2552  * server functions.
2553  *
2554  * @param lang Requested language of description
2555  * @return Description of this module
2556  */
2557  @Override
2558  public String getModuleDescription(int lang) {
2559  return "Core module perform basic document, types and annotations manipulations.";
2560  }
2561 
2562  /**
2563  * Normalizes URI and removes id of session and other mess.
2564  *
2565  * @param uri URI to clean up
2566  * @return Cleaned URI
2567  */
2568  public static String cleanUpURI(String uri) throws URISyntaxException {
2569  URI jUri = new URI(uri);
2570  // normalize URI
2571  jUri = jUri.normalize();
2572  // convert back to string
2573  String cUri = jUri.toString();
2574 
2575  // find fragment part
2576  int fIndex = cUri.lastIndexOf("#");
2577  if (fIndex > 0) { // if fragment was found, remove it
2578  cUri = cUri.substring(0, fIndex);
2579  }
2580 
2581  // find beginning of parameters
2582  int pIndex = cUri.indexOf('?');
2583  if (pIndex < 0) { // if there are no parameters
2584  return cUri; // uri is clean
2585  }
2586 
2587  // Remove unnecessary parameters
2588  int length = cUri.length(); // get length of string
2589  int i = pIndex + 1;
2590  while (i < length && i > 0) { // traverse URI
2591  boolean rem = false; // was parameter removed in this round of cycle?
2592  int eInd = cUri.indexOf("&", i); // find end of parameter value
2593  if (eInd < 0) { // if it is last parameter
2594  eInd = length - 1; // parameter ends at the end of the string
2595  }
2596  Iterator<String> pIt = Constants.REM_URI_PARAMS.iterator();
2597  int sInd = -1;
2598  while (pIt.hasNext() && sInd != i) { // for each parameter to bee removed
2599  String rParam = pIt.next();
2600  sInd = cUri.indexOf(rParam, i); // find parameter
2601  }
2602  if (sInd == i) { // if parameter is at the beginning of the string
2603  rem = true;
2604  cUri = cUri.substring(0, i) + cUri.substring(eInd + 1, length); // remove parameter
2605  length = cUri.length(); // actualize length
2606  }
2607  if (!rem) { // if parameter wasn't removed
2608  i = cUri.indexOf("&", i) + 1; // move to beginning of next parameter
2609  }
2610  } // traverse URI
2611 
2612  if (cUri.endsWith("?")) { // if "&" is at the end, remove it
2613  cUri = cUri.substring(0, length - 1);
2614  length--;
2615  }
2616  if (cUri.endsWith("&")) { // if "?" is at the end, remove it
2617  cUri = cUri.substring(0, length - 1);
2618  }
2619  return cUri;
2620  } // cleanUpURI()
2621 
2622  // TODO (at global level) Howto make linearization possible:
2623  // - Implement synchronizeLinearizedDocument() and reSynchronizeLinearizedDocument()
2624  // - Implement toXMLStringLin() for annotation and use this instead of toXMLString() if linearization is enabled
2625  // - Implement toXMLStringLin() for TextModification
2626  // - Add conversions of linearized data to MessageProcessor
2627  //
2628  // If linearized document may be handled (without having structured version), there are two possibilities:
2629  // - Create simple structured document from linearized in synchronization
2630  // - Make a lot of changes on a lot of places
2631  // * @param doc persisted annotated document whether exists, otherwise null
2632  // * @param newDoc New version of annotated document
2634  // TODO: Implement required functionality and remove this.
2635  int langNum = requestInfo.getSession().getLanguageNum();
2636  int lod = requestInfo.getSession().getProtocolLOD();
2637  if (lod < Constants.PROTOCOL_LOD_V2) {
2638  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC);
2639  requestInfo.addError(lod, langNum, Localisation.ERROR_100_UNKNOWN_ERROR, "", "Linearization not yet implemented.");
2640  } else {
2641  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC);
2642  requestInfo.addError(lod, langNum, Localisation.ERROR_99_UNSUPPORTED_OPERATION);
2643  }
2645  String msg = "Attempt to synchronize linearized document (not yet implemented).";
2646  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2647  }
2648  // This must be used in this function:
2649  // requestInfo.getSession().setLinearization(true);
2650  }
2651 
2653  // TODO: Implement required functionality and remove this.
2654  int langNum = requestInfo.getSession().getLanguageNum();
2655  int lod = requestInfo.getSession().getProtocolLOD();
2656  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC);
2657  requestInfo.addError(lod, langNum, Localisation.ERROR_100_UNKNOWN_ERROR, "", "Linearization not yet implemented.");
2659  String msg = "Attempt to resynchronize linearized document (not yet implemented).";
2660  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2661  }
2662  }
2663 
2664  /**
2665  * Generates text modifications and saves them into flier.
2666  *
2667  * @param oldVersion Old version of document against which to generate text modifications.
2668  * @param newVersion New version of document.
2669  * @param linearized Specifies whether should be generated linearized text modifications or structured.
2670  * @param requestInfo Informations about client request
2671  * @return True on success, false on fail.
2672  */
2673  private void generateTextModificationsToFlier(String oldVersion, String newVersion, boolean linearized, RequestInfo requestInfo) {
2674  Flier flier = requestInfo.getFlier();
2675 
2676  // TODO: Work with linearized document should be handled separately
2677  // as text modification provider below doesn't support it.
2678 
2679  ArrayList<TextModification> tmsUnit;
2680  try{
2681  tmsUnit = TextModificationsXMLUnitProvider.getTextModifications
2682  (oldVersion, newVersion, getLowestProtocolFromSessionList(requestInfo));
2683  }
2684  catch(Exception ex){
2686  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, ex.getMessage());
2687  }
2688  tmsUnit = new ArrayList<TextModification>();
2689  tmsUnit.add(new TextModification(XPathHelper.DOCUMENT_XPATH, null, null, newVersion));
2690  }
2691  flier.setTextModifications(tmsUnit);
2692  }
2693 
2694  /**
2695  * Gets the lowest version of the protocol used in
2696  * the clients where is the actual session document used.
2697  * @param requestInfo actual requestInfo
2698  * @return Lowest version of the protocol
2699  */
2700  private static int getLowestProtocolFromSessionList(RequestInfo requestInfo){
2701  EditorSession actualSession = requestInfo.getSession();
2702  int protocol = actualSession.getProtocolLOD();
2703 
2704  ArrayList<EditorSession> sesList = AppBean.getSessions();
2705  if(sesList != null){
2706  Iterator<EditorSession> it = sesList.iterator();
2707  while(it.hasNext()){
2708  EditorSession ses = it.next();
2709  if(actualSession.getSyncDocument().getId() == ses.getSyncDocument().getId() && ses.getProtocolLOD() < protocol){
2710  protocol = ses.getProtocolLOD();
2711  }
2712  }
2713  }
2714  return protocol;
2715  }
2716 
2717  /**
2718  * Updates document with text modifications from request.
2719  * If update finishes with success, stores text modifications into flier.
2720  * If update fails, generates reverse text modifications.
2721  *
2722  * @param requestInfo Informations about client request
2723  * @return String with messages for client or empty string
2724  */
2725  private String updateDocument(RequestInfo requestInfo) {
2726  int langNum = requestInfo.getSession().getLanguageNum();
2727  int lod = requestInfo.getSession().getProtocolLOD();
2728  String retStr = "";
2729  Flier flier = requestInfo.getFlier();
2730 
2731  if (requestInfo.getTextModifications().isEmpty()) {
2732  return retStr;
2733  }
2734 
2735  requestInfo.setDocumentChanged(true);
2736  EditorSession session = requestInfo.getSession();
2737  AnnotDocument doc = session.getSyncDocument();
2738 
2739  if (doc == null) {
2740  requestInfo.addError(lod, langNum, Localisation.ERROR_43_NOT_SYNCHRONIZED);
2741  return retStr;
2742  }
2743 
2744  int lastModID = 0;
2745 
2746  if(doc != null){
2747  // lock for change
2748  while(true){
2749  if(AppBean.getLockMaster().getDocumentLock(doc.getId()).lockForChange()){
2750  break;
2751  }
2752  }
2753 
2754  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2) {
2755  lastModID = AppBean.getLastDocModification(doc.getId());
2756  // If the modification number from the request and the saved modification number for the document are the same
2757  // Do update
2758  if (lastModID != requestInfo.getLastModificationNumber()){
2759  // If they are not the same check similiarity in last three modification set IDs
2760  // If the sent modification ID is the same with the last three applied modifications
2761  // then check these modification sets for conflicts
2762  if(!AppBean.lastModificationSetsContains(doc.getId(), lastModID) ||
2763  AppBean.hasModificationSetsConflicts(doc.getId(), lastModID, requestInfo.getTextModifications()))
2764  {
2765  // send text modification error and cancel text modifications with error
2766  AppBean.getLockMaster().getDocumentLock(doc.getId()).unlockForChange();
2767  String info = "<modification id=\"" + requestInfo.getLastModificationNumber()
2768  + "\" mustApply=\"" + lastModID + "\"/>";
2769  requestInfo.addError(lod, langNum, Localisation.ERROR_39_MODIFICATION_NOT_APPLICABLE, info);
2770 
2772  String msg = "An error has occured while trying to apply text modications: modification conflict";
2773  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg, new DocumentUpdateException());
2774  }
2775  return retStr;
2776  }
2777  }
2778  }
2779  }
2780 
2781  //Prepare document for update
2782  DocumentUpdate docUpdate = new DocumentUpdate(doc.getContent(), session);
2783  docUpdate.appendModifications(requestInfo.getTextModifications());
2784 
2785  boolean failed = false;
2786 
2787  try{
2788  docUpdate.updateDocument();
2789  doc.setModified(new Date());
2790  doc.setContent(docUpdate.getDocumentString());
2791  session.setParsedLinDocument(Linearizer.linearizeDocument(docUpdate.getDocument()));
2792  try {
2793  session.setParsedSyncDocument(this.matcherProvider.getDocumentFromString(doc.getContent(), false, true));
2794  } catch(Exception e){
2795  langNum = requestInfo.getSession().getLanguageNum();
2796  lod = requestInfo.getSession().getProtocolLOD();
2797  String info = "<module name=\"" + getModuleName(requestInfo.getSession().getLanguageNum()) + "\"/>";
2798  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, info);
2799  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
2801  String msg = "Parsing updated document for the parsed synchronized document failed.";
2802  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2803  }
2804  return null; // synchronization failed
2805  }
2806  flier.setTextModifications(requestInfo.getTextModifications());
2807  }
2808  catch(DocumentUpdateException ex){
2809  failed = true;
2810 
2812  requestInfo.addError(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT);
2813  String info;
2814  if (lod == Constants.PROTOCOL_LOD_V2) {
2815  info = "<resynchronize resource=\"" + requestInfo.getSession().getSyncDocument().getUri()
2816  + "\" method=\"hard\"/>";
2817  } // Protocol 1.x is used
2818  else {
2819  info = "<resynchronize/>";
2820  }
2821  requestInfo.getSession().addMessageTS(info);
2822  }
2823 
2824  ArrayList<TextModification> failedReversedMods = ex.getReverseModifications();
2825  /*
2826  * If exception's flag for resync is set, it's needed to inform client to make a resync
2827  * This is only done if client is using v1.x protocol version
2828  *
2829  * NOTE: This is special case of interruption process of text modifications when there was unable to
2830  * generate reverse modification, so it could not be able to insert it into failedModification list to exception
2831  *
2832  * NOTE: Resynchronization flag can be set only for protocol 1.x
2833  */
2834  if(ex.getResyncFlag() && session.getProtocolLOD() < Constants.PROTOCOL_LOD_V2){
2835 
2837  String msg = "Unable to generate reverse text modification.";
2838  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg, ex);
2839  }
2840  requestInfo.addError(lod, langNum, Localisation.ERROR_11_SYNC_ERROR_NEED_RESYNC, "<resynchronize/>");
2841  requestInfo.appendToReply("<resynchronize/>");
2842  }
2843  /*
2844  * If an exception has occured, and client is using protocol v1.x, it's needed to sent to client all failed reverse modifications
2845  */
2846  else if(failedReversedMods != null && !failedReversedMods.isEmpty() && session.getProtocolLOD() < Constants.PROTOCOL_LOD_V2){
2847  Iterator<TextModification> textModIt = failedReversedMods.iterator();
2848  while(textModIt.hasNext()){
2849  TextModification tm = textModIt.next();
2850 
2851  //Add modification into client message
2852  requestInfo.addError(tm.toXMLString());
2853  }
2854  }
2855  /*
2856  * If modification has failed, and client is using protocol version 2, all already applied modifications are reversed and client is
2857  * informed that an error has occured
2858  */
2859  else if(ex.getErrorMessage() != null && !ex.getErrorMessage().isEmpty()){
2861  String context = "<modification id=\"" + requestInfo.getLastModificationNumber() + "\"/>";
2862  requestInfo.addError(lod, langNum, Localisation.ERROR_105_MODIFICATION_SPECIFICATION, context);
2863  }
2865  String context = "<module name=\"" + getModuleName(langNum) + "\"/>";
2866  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, context);
2867  }
2869  String msg = "An error has occured while trying to apply text modications: " + ex.getErrorMessage();
2870  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg, ex);
2871  }
2872  }
2873  }
2874 
2875  if (doc != null && !failed) {
2876  Integer lastMod = AppBean.incrementLastDocModification(doc.getId());
2877  if (lastMod == null) {
2878  String msg = "Unable to increment text modification set ID.";
2880  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2881  }
2882  requestInfo.addError(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, msg);
2883  } else {
2884  flier.setTextModificationSetID(lastMod);
2885  AppBean.addDocModificationSet(doc.getId(), lastMod, requestInfo.getTextModifications());
2886  }
2887  }
2888 
2889 
2890  // If modification process was succesfull apply text modifications to another sessions
2891  if (!failed) {
2892  synchronized (AppBean.getSessions()) {
2893  Iterator<EditorSession> sessionsIt = AppBean.getSessions().iterator();
2894  while (sessionsIt.hasNext()) { // for each session
2895 
2896  EditorSession actualSession = sessionsIt.next();
2897 
2898  if (session != null && flier.getCreatedInSessId() != null
2899  && flier.getCreatedInSessId() != actualSession.getSessionId()) { // it is not session in flier
2900 
2901  AnnotDocument currDoc = actualSession.getSyncDocument();
2902 
2903  // Continue whether current document is not synchronized, or is not the same
2904  if (doc != null && currDoc != null && currDoc.getId() == doc.getId()
2905  && requestInfo.getTextModifications() != null && !requestInfo.getTextModifications().isEmpty()) { // same document
2906 
2907  //Prepare document for update
2908  DocumentUpdate actDocUpdate = new DocumentUpdate(currDoc.getContent(), actualSession);
2909  actDocUpdate.appendModifications(requestInfo.getTextModifications());
2910 
2911  try {
2912  actDocUpdate.updateDocument();
2913  currDoc.setContent(actDocUpdate.getDocumentString());
2914  currDoc.setModified(new Date());
2915  actualSession.setParsedLinDocument(Linearizer.linearizeDocument(actDocUpdate.getDocument()));
2916  try {
2917  actualSession.setParsedSyncDocument(this.matcherProvider.getDocumentFromString(doc.getContent(), false, true));
2918  } catch(Exception e){
2919  langNum = actualSession.getLanguageNum();
2920  lod = actualSession.getProtocolLOD();
2921  actualSession.addMessageTS(RequestInfo.createErrorMsg(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT));
2922  String info = "";
2923  if (lod == Constants.PROTOCOL_LOD_V2) {
2924  info = "<resynchronize resource=\"" + actualSession.getSyncDocument().getUri()
2925  + "\" method=\"hard\"/>";
2926  } // Protocol 1.x is used
2927  else {
2928  info = "<resynchronize/>";
2929  }
2930  actualSession.addMessageTS(info);
2932  String msg = "Parsing updated document for the parsed synchronized document while updating other sessions failed.";
2933  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
2934  }
2935  return null; // synchronization failed
2936  }
2937  } catch (DocumentUpdateException ex) {
2939  langNum = actualSession.getLanguageNum();
2940  lod = actualSession.getProtocolLOD();
2941  actualSession.addMessageTS(RequestInfo.createErrorMsg(lod, langNum, Localisation.ERROR_36_BAD_DOCUMENT));
2942  }
2944  String context = "<module name=\"" + getModuleName(langNum) + "\"/>";
2945  actualSession.addMessageTS(RequestInfo.createErrorMsg(lod, langNum, Localisation.ERROR_33_MODULE_ERROR, context));
2946  }
2947 
2949  String msg = "Updating of document has failed.";
2950  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.ALL, msg);
2951  }
2952 
2953  String info;
2954  // Protocol V2 is used
2955  if(requestInfo.getSession().getProtocolLOD() == Constants.PROTOCOL_LOD_V2){
2956  info = "<resynchronize resource=\"" + requestInfo.getSession().getSyncDocument().getUri()
2957  + "\" method=\"hard\"/>";
2958  }
2959  // Protocol 1.x is used
2960  else{
2961  info = "<resynchronize/>";
2962  }
2963  actualSession.addMessageTS(info);
2964  actualSession.notifyComet();
2965 
2966  continue;
2967  }
2968  } // same document
2969  } // it is not session in flier
2970  } // for each session
2971  } // synchronized
2972  } // If modification process was succesfull ...
2973 
2974  if(doc != null) {
2975  if(session.getProtocolLOD() == Constants.PROTOCOL_LOD_V2) {
2976  retStr = "<modificationApplied id=\"" + (lastModID + 1) + "\"/>";
2977  }
2978  // unlock for change
2979  AppBean.getLockMaster().getDocumentLock(doc.getId()).unlockForChange();
2980  }
2981 
2982 
2983  return retStr;
2984  } // updateDocument()
2985 
2986  /**
2987  * Gets the user sources
2988  *
2989  * @param user user
2990  * @return arraylist of strings with sources
2991  */
2992  static public ArrayList<String> getUserSources(User user) {
2993  ArrayList<String> result = new ArrayList<String>();
2994 
2995  if (user.getComeFrom() != null) {
2996  result.add(user.getComeFrom());
2997  }
2998 
2999  List<UserGroup> groups = user.getGroups();
3000  if (groups != null) {
3001  Iterator groupsIt = groups.iterator();
3002  while (groupsIt.hasNext()) {
3003  result.add(((UserGroup) groupsIt.next()).getUri());
3004  }
3005  }
3006 
3007  return result;
3008  }
3009 
3010  /**
3011  * Return all annotations that are related with annotations in list
3012  *
3013  * @param annotations list of annotations to search
3014  * @return list of related annotations and given annotations
3015  */
3016  private ArrayList<Annotation> getRelatedAnnotations(ArrayList<Annotation> annotations){
3017  ArrayList<Annotation> openList = new ArrayList<Annotation>();
3018  ArrayList<Annotation> closeList = new ArrayList<Annotation>();
3019 
3020  if(!annotations.isEmpty())
3021  {
3022  //insert approved annotations
3023  openList.addAll(annotations);
3024  while(!openList.isEmpty()){
3025  //take first from open list
3026  Annotation first = openList.remove(0);
3027  //test if first from open is not in close list
3028  if(!closeList.contains(first)){
3029  //get all pointers to first
3030  openList.addAll(getLinkedAnnotationsDB(first));
3031  if(!annotations.contains(first)){
3032  closeList.add(first);
3033  }
3034  }
3035  }
3036 
3037  return closeList;
3038  }
3039 
3040  return openList;
3041  }
3042 
3043  /**
3044  * Gets annotations for update
3045  *
3046  * @param annotations List of Annotations
3047  * @param updatedAnnots List of updated Annotations
3048  * @return empty arraylist
3049  */
3050  private ArrayList<Annotation> getAnnotsForUpdate(ArrayList<Annotation> annotations, ArrayList<Annotation> updatedAnnots){
3051  ArrayList<Annotation> changedList = new ArrayList<Annotation>();
3052 
3053  Iterator<Annotation> annotIt = annotations.iterator();
3054  while(annotIt.hasNext()){
3055  Annotation currentAnnot = annotIt.next();
3056  //get all annotations parameters that have link parameter that points to currentAnnot
3057  Object[] params = {"linked", currentAnnot.getId()};
3058  @SuppressWarnings("unchecked")
3059  List<LinkedAnnotationAttribute> attrList = AppBean.getPersistenceManager().queryDB("Attribute.findByLinked", params);
3060  if (attrList != null) {
3061  Iterator<LinkedAnnotationAttribute> attrIt = attrList.iterator();
3062 
3063  //go trough all results and set their values to null
3064  while(attrIt.hasNext()){
3065 
3066  LinkedAnnotationAttribute currentAttr = attrIt.next();
3067 
3068  //add annotation that this parameter belongs to changed array
3069  Annotation annotToAdd = ((BaseAttribute)currentAttr).getRefAnnotation();
3070  if(!updatedAnnots.contains(annotToAdd)){
3071  // set attribute to null
3072  currentAttr.setValue(null);
3073  updatedAnnots.add(annotToAdd);
3074  }else{
3075  // annotation is already in list
3076  // try to merge current annotation with changed annotation
3077  Annotation updatedAnnot = updatedAnnots.get(updatedAnnots.indexOf(annotToAdd));
3078  if(updatedAnnot.getAttributes() != null){
3079  // go trough all atributes of annotation that is alredy stored in updatedAnnotations list
3080  Iterator<BaseAttribute> updatedAnnotAttrIt = updatedAnnot.getAttributes().iterator();
3081  while(updatedAnnotAttrIt.hasNext()){
3082  BaseAttribute currAttr = updatedAnnotAttrIt.next();
3083  // compare by IDs
3084  if(currAttr.getId().equals(currentAttr.getId())){
3085  // if finds corresponding attribute, set its value to null
3086  currAttr.setValue(null);
3087  }
3088  }
3089  }
3090  }
3091  }
3092  }
3093  }
3094 
3095  //changedList = getLinkedAnnotations(changedList);
3096 
3097  return changedList;
3098  }
3099 
3100  private ArrayList<String> getAllNestedIdentificators(Annotation annot){
3101  ArrayList<String> result = new ArrayList<String>();
3102  if(annot.getAttributes() != null){
3103  Iterator<BaseAttribute> annotAttrIt = annot.getAttributes().iterator();
3104  while(annotAttrIt.hasNext()){
3105  BaseAttribute currentBaseAttr = annotAttrIt.next();
3106  if(currentBaseAttr.getNestedAnnotation() != null){
3107  if(!result.contains(currentBaseAttr.getNestedAnnotation().getURIV2())){
3108  result.add(currentBaseAttr.getNestedAnnotation().getURIV2());
3109  }
3110  }
3111  }
3112  }
3113  return result;
3114  }
3115 
3116  /**
3117  * Update all annotations with relationships to given approved annotations in list.
3118  *
3119  * @param approvedAnnotations list of approved annotations
3120  * @return list of related annotations and given annotations
3121  */
3122  private ArrayList<Annotation> getLinkedAnnotations(ArrayList<Annotation> approvedAnnotations){
3123  ArrayList<Annotation> openList = new ArrayList<Annotation>();
3124  ArrayList<Annotation> closeList = new ArrayList<Annotation>();
3125 
3126  if(!approvedAnnotations.isEmpty())
3127  {
3128  //insert approved annotations
3129  openList.addAll(approvedAnnotations);
3130  while(!openList.isEmpty()){
3131  //take first from open list
3132  Annotation first = openList.remove(0);
3133  //test if first from open is not in close list
3134  if(!closeList.contains(first)){
3135  //get all pointers to first
3136  openList.addAll(getLinkedAnnotationsDB(first));
3137  closeList.add(first);
3138  }
3139  }
3140 
3141  return closeList;
3142  }
3143 
3144  return approvedAnnotations;
3145  }
3146 
3147  /**
3148  * Gets linked annotation attributes from db which links input annotation
3149  *
3150  * @param annot input annotations
3151  * @return arraylist of linked annotations
3152  */
3153  public static ArrayList<Annotation> getLinkedAnnotationsDB(Annotation annot) {
3154  ArrayList<Annotation> innerAnnotationList = new ArrayList<Annotation>();
3155  Object[] params = {"linked", annot.getId()};
3156  @SuppressWarnings("unchecked")
3157  List<LinkedAnnotationAttribute> attrList = AppBean.getPersistenceManager().queryDB("Attribute.findByLinked", params);
3158  if (attrList != null) {
3159  Iterator<LinkedAnnotationAttribute> attrIt = attrList.iterator();
3160 
3161  while (attrIt.hasNext()) {
3162  LinkedAnnotationAttribute linkedAttr = attrIt.next();
3163  Annotation dbAnnot = (Annotation) AppBean.getPersistenceManager().getEntityById("Annotation", linkedAttr.getAnnotation());
3164  if (dbAnnot != null) {
3165  innerAnnotationList.add(dbAnnot);
3166  }
3167  }
3168  }
3169  return innerAnnotationList;
3170  }
3171 
3172  /**
3173  * Returns difference of the annotation lists ( A / B ).
3174  *
3175  * @param annotListA List A
3176  * @param annotListB List B
3177  * @return Difference
3178  */
3179  public static ArrayList<Annotation> getDifference(ArrayList<Annotation> annotListA, ArrayList<Annotation> annotListB) {
3180  ArrayList<Annotation> result = new ArrayList<Annotation>();
3181  Iterator<Annotation> iteratorA = annotListA.iterator();
3182  while (iteratorA.hasNext()) {
3183  Annotation actualAnnot = iteratorA.next();
3184  Iterator<Annotation> iteratorB = annotListB.iterator();
3185  boolean isInB = false;
3186  while (iteratorB.hasNext()) {
3187  if (actualAnnot.getId().equals(iteratorB.next().getId())) {
3188  isInB = true;
3189  }
3190  }
3191  if(!isInB){
3192  result.add(actualAnnot);
3193  }
3194  }
3195 
3196  return result;
3197  }
3198 
3199  /**
3200  * Return all annotations that are related with annotations in lists for session in parameter
3201  * (not related only to approvedAnnot list but also to something to which is user subscribed)
3202  *
3203  * @param approvedAnnot approved annotation list
3204  * @param unapprovedAnnot unapproved annotation list
3205  * @param session session
3206  * @return list of related annotations and given annotations
3207  */
3208  public static ArrayList<Annotation> getRelatedAnnotations(ArrayList<Annotation> approvedAnnot, ArrayList<Annotation> unapprovedAnnot, EditorSession session) {
3209  ArrayList<Annotation> result = new ArrayList<Annotation>();
3210  Iterator<Annotation> unapprovedIt = unapprovedAnnot.iterator();
3211 
3212  // we will inspect unapproved annotations one by one
3213  while (unapprovedIt.hasNext()) {
3214  // annotations that will be tested will be in this list
3215  ArrayList<Annotation> openList = new ArrayList<Annotation>();
3216  // there will be annotations after test
3217  ArrayList<Annotation> closedList = new ArrayList<Annotation>();
3218  // We will put just inspected annotation into the openList. Then it will
3219  // be removed and put to the start of the closedList where it will remain.
3220  openList.add(unapprovedIt.next());
3221 
3222  // Now we will be searching approved annotation which is directly
3223  // or indirectly linking inspected one.
3224  while (!openList.isEmpty()) {
3225  Annotation actualAnnot = openList.remove(0);
3226  closedList.add(actualAnnot);
3227 
3228  if (session.isSubscribed(actualAnnot, getUserSources(actualAnnot.getUser()))) {
3229  // We found something which is directly or indirectly linking inspected one
3230  // and for which is user subscribed. We do not know whether the subscribed
3231  // one is in approvedAnnot list but we need to be sure that user gets all what
3232  // he need so we should approve inspected one (it is related with approved
3233  // or with something other which user potentially have).
3234 
3235  // The inspected annotation is inserted on the first place in the list
3236  // and it is considered a resulting related annotation that is somehow
3237  // linked by some annotation that matches the user's subscription list
3238  // so we add inspected one to the resulting list.
3239  result.add(closedList.get(0));
3240  // after the subscribtion status is confirmed (inspected annotation
3241  // is approved) it is not necessary to inspect another related annotations
3242  // which are linking inspected one and potentially can conform user's subscriptions
3243  break;
3244  } else {
3245  // Get annotations which are linking actual annotation, then get annotatinos
3246  // that are not in close list then in open list.
3247  // On the end is in annotForAdd list only annotations that linked
3248  // at actual annotation and they ar not in close and open list.
3249  ArrayList<Annotation> annotForAdd = getDifference(getDifference(getLinkedAnnotationsDB(actualAnnot), closedList), openList);
3250  openList.addAll(annotForAdd);
3251  }
3252  }
3253  }
3254 
3255  if(approvedAnnot == null){
3256  return result;
3257  }
3258 
3259  approvedAnnot.addAll(result);
3260  return approvedAnnot;
3261  }
3262 
3263  /**
3264  * Checks all AnnotationLink attributes for values and updates refferences if
3265  * attribute value is null and tmpId value of attribute != null.
3266  *
3267  * @param annot Annotation for check.
3268  * @param requestInfo Informations about client request
3269  */
3270  private void updateAnnotationLinks(Annotation annot, RequestInfo requestInfo) {
3271  ArrayList<SuggestionLogEntry> suggested = requestInfo.getConfirmedSuggestions();
3272 
3273  List attr = annot.getAttributes();
3274  Iterator it = attr.iterator();
3275  while (it.hasNext()) { // for all attributes of Annotation
3276  BaseAttribute currAttr = (BaseAttribute) it.next();
3277  if (currAttr.getSimpleType().equals("AnnotationLink") && currAttr.getValue() == null) {
3278  String tmpId = ((LinkedAnnotationAttribute) currAttr).getTmpId();
3279  boolean found = false;
3280  if (tmpId != null) {
3281  Iterator<SuggestionLogEntry> suggIt = suggested.iterator();
3282  while (suggIt.hasNext() && !found) {
3283  SuggestionLogEntry curSug = suggIt.next();
3284  if (curSug.getTmpId().equals(tmpId)) {
3285  Annotation annotHit = curSug.getConfirmedVersion();
3286  if (annotHit != null) {
3287  currAttr.setValue(annotHit);
3288  if(annotHit != null){
3289  currAttr.setAttributeType(annotHit.getAnnotType());
3290  }
3291  found = true;
3292  }
3293  }
3294  }
3295  if (found == false) { // Attribute with right tmpId wasn't found.
3296  it.remove();
3297  int langNum = requestInfo.getSession().getLanguageNum();
3298  int lod = requestInfo.getSession().getProtocolLOD();
3299  if (lod < Constants.PROTOCOL_LOD_V2) {
3300  requestInfo.addError(lod, langNum, Localisation.ERROR_7_ATTRIBUTE_VALUE);
3301  } else {
3302  String info = "<attribute annotationUri=\"" + annot.getURIV2()
3303  + "\" uri=\"" + currAttr.getUriInOntology()
3304  + "\" name=\"" + currAttr.getName()
3305  + "\" valueType=\"" + currAttr.getValueType()
3306  + "\" type=\"" + currAttr.getTypeUriV2() + "\"/>";
3307  requestInfo.addError(lod, langNum, Localisation.ERROR_7_ATTRIBUTE_VALUE, info);
3308  }
3310  String msg = "Annonation link wasn't found. TmpId = " + tmpId + " .";
3311  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
3312  }
3313  }
3314  }
3315  } else if (currAttr.getSimpleType().equals("NestedAnnotation") && currAttr.getValue() != null) {
3316  updateTmpIdRefInNested(currAttr.getNestedAnnotation(), requestInfo);
3317  }
3318  } // for all attributes of Annotation
3319  } // checkAnnotationLinks()
3320 
3321  /**
3322  * Recursively Checks all AnnotationLink attributes in NestedAnnotations for
3323  * values and updates refferences if attribute value is null and tmpId value
3324  * of attribute != null.
3325  *
3326  * @param nestedAnn Nested Annotation link
3327  * @param requestInfo Informations about client request
3328  */
3329  private static void updateTmpIdRefInNested(Annotation nestedAnn, RequestInfo requestInfo) {
3330  ArrayList<SuggestionLogEntry> suggested = requestInfo.getConfirmedSuggestions();
3331 
3332  List<BaseAttribute> attr = nestedAnn.getAttributes();
3333  Iterator<BaseAttribute> aIt = attr.iterator();
3334  while (aIt.hasNext()) { // for each attribute
3335  BaseAttribute attribute = aIt.next();
3336  if (attribute.getSimpleType().equals("AnnotationLink")
3337  && ((LinkedAnnotationAttribute) attribute).getValue() == null
3338  && ((LinkedAnnotationAttribute) attribute).getTmpId() != null) {
3339  Iterator<SuggestionLogEntry> suggIt = suggested.iterator();
3340  String tmpId = ((LinkedAnnotationAttribute) attribute).getTmpId();
3341  boolean found = false;
3342  while (suggIt.hasNext() && !found) {
3343  SuggestionLogEntry curSug = suggIt.next();
3344  if (curSug.getTmpId().equals(tmpId)) {
3345  Annotation annotHit = curSug.getConfirmedVersion();
3346  if (annotHit != null) {
3347  attribute.setValue(annotHit);
3348  if(annotHit != null){
3349  attribute.setAttributeType(annotHit.getAnnotType());
3350  }
3351  found = true;
3352  }
3353  }
3354  }
3355  if (found == false) { // Attribute with right tmpId wasn't found.
3356  aIt.remove();
3357  int langNum = requestInfo.getSession().getLanguageNum();
3358  int lod = requestInfo.getSession().getProtocolLOD();
3359  if (lod < Constants.PROTOCOL_LOD_V2) {
3360  requestInfo.addError(lod, langNum, Localisation.ERROR_7_ATTRIBUTE_VALUE);
3361  } else {
3362  String info = "<attribute annotationUri=\"" + nestedAnn.getURIV2()
3363  + "\" uri=\"" + attribute.getUriInOntology()
3364  + "\" name=\"" + attribute.getName()
3365  + "\" valueType=\"" + attribute.getValueType()
3366  + "\" type=\"" + attribute.getTypeUriV2() + "\"/>";
3367  requestInfo.addError(lod, langNum, Localisation.ERROR_7_ATTRIBUTE_VALUE, info);
3368  }
3370  String msg = "Annonation link wasn't found.";
3371  Logger.getLogger(CoreFuncModule.class.getName()).log(Level.SEVERE, msg);
3372  }
3373  }
3374  } else if (attribute.getSimpleType().equals("NestedAnnotation") && attribute.getNestedAnnotation() != null) {
3375  updateTmpIdRefInNested(attribute.getNestedAnnotation(), requestInfo);
3376  }
3377  } // for each attribute
3378  } // updateTmpIdRefInNested
3379 
3380  /**
3381  * If document can be modified by this request, returns true, false otherwise
3382  *
3383  * @param requestInfo Informations about client request
3384  * @return If document can be modified by this request, returns true, false otherwise
3385  */
3386  private boolean canBeDocumentModified(RequestInfo requestInfo) {
3387  if (requestInfo.getFlier() != null) {
3388  if (requestInfo.getFlier().getTextModifications() != null && !requestInfo.getFlier().getTextModifications().isEmpty()) {
3389  return true;
3390  }
3391  }
3392  if (requestInfo.getResyncDocContent() != null) {
3393  return true;
3394  }
3395  if (requestInfo.getSyncDocumentData() != null) {
3396  return true;
3397  }
3398  return false;
3399  } // canBeDocumentModified()
3400 
3401 } // class CoreFuncModule
String makeAddedAnnotMsg(ArrayList< Annotation > addedAnnotations, EditorSession session, Flier flier, boolean proto11)
static synchronized boolean hasModificationSetsConflicts(int documentID, int setID, ArrayList< TextModification > modifications)
Definition: AppBean.java:608
static ArrayList< Annotation > getLinkedAnnotationsDB(Annotation annot)
Class representing attribute of unknown type of annotation.
static void updateTmpIdRefInNested(Annotation nestedAnn, RequestInfo requestInfo)
String makeDeletedAnnotMsg(ArrayList< Annotation > deletedAnnotations, ArrayList< Annotation > deletedNestedAnnotations, EditorSession session, boolean proto11)
boolean isInJustAdded(String serverUri)
Definition: Flier.java:469
void addNestedToRemoved(Annotation annot, List< Annotation > removedNested)
static ArrayList< Annotation > getDifference(ArrayList< Annotation > annotListA, ArrayList< Annotation > annotListB)
String getQueriedTypeLike(String qString, RequestInfo requestInfo)
static final double MIN_XPATH_LD_DIFF_TO_AUTO_MODIFY_FR
Definition: Constants.java:308
ArrayList< Annotation > getAnnotsForUpdate(ArrayList< Annotation > annotations, ArrayList< Annotation > updatedAnnots)
Class representing attribute of type of annotation.
boolean isInSubscribeList(Subscription subscription)
ArrayList< TextModification > getTextModifications()
String processRequestBeforePersist(RequestInfo requestInfo)
void updateAnnotationLinks(Annotation annot, RequestInfo requestInfo)
double isAnnotationAffected(Annotation annotation, AnnotDocument newDoc, RequestInfo requestInfo)
double fragmentTreshold(Fragment origFragment, UpdatableFragment foundFragment)
Class representing annotated copy of document.
Document, annotations and types manipulation functionality.
static HashMap< String, ReentrantLock > documentURILocker
Definition: AppBean.java:78
boolean canBeDocumentModified(RequestInfo requestInfo)
Singleton for storing global variables.
Definition: AppBean.java:47
ArrayList< String > getAllNestedIdentificators(Annotation annot)
String getQueriedSubscriptions(RequestInfo requestInfo)
Class providing access to available matchers.
String messagesFromFlier(Flier flier, EditorSession session)
boolean typeInClientCache(AnnotType type, ArrayList< String > queries)
void reSynchronizeStructuredDocument(AnnotDocument doc, AnnotDocument newDoc, RequestInfo requestInfo)
ArrayList< SuggestionLogEntry > getAutoconfirmedSuggestions()
void generateTextModificationsToFlier(String oldVersion, String newVersion, boolean linearized, RequestInfo requestInfo)
int checkAnnotationFragments(Annotation annotation, AnnotDocument annotDoc, RequestInfo requestInfo, Boolean cmpErr, Integer updFr)
String getAllInterestingAnnotations(RequestInfo requestInfo)
String getQueriedTypeExact(String qString, RequestInfo requestInfo)
boolean fragmentEqualsWUF(UpdatableFragment uf)
Definition: Fragment.java:396
ArrayList< Annotation > getLinkedAnnotations(ArrayList< Annotation > approvedAnnotations)
boolean isTypeInGroups(AnnotType annotType, EditorSession session)
ArrayList< Subscription > getUnsubscribeList()
Suggested annotation with informations about suggestion.
Static class which parses and process XML with messages.
Class representing user group.
Definition: UserGroup.java:47
void reSynchronizeLinearizedDocument(AnnotDocument doc, AnnotDocument newDoc, RequestInfo requestInfo)
void findLinkingAnnotationsIds(Annotation remAnnot, List< Integer > closed)
static ArrayList< String > getUserSources(User user)
void synchronizeStructuredDocument(AnnotDocument doc, AnnotDocument newDoc, RequestInfo requestInfo)
static final double MAX_XPATH_LD_TO_AUTO_MODIFY_FR
Definition: Constants.java:303
boolean containsMoreThanTwoAsterisks(String pattern, String wildcard)
String makeChangedAnnotMsg(ArrayList< Annotation > changedAnnotations, EditorSession session, boolean proto11)
Base class representing attribute of annotation.
static final double MAX_TEXT_LD_TO_AUTO_MODIFY_FR
Definition: Constants.java:312
ArrayList< Annotation > getRelatedAnnotations(ArrayList< Annotation > annotations)
Class representing type of annotation.
Definition: AnnotType.java:58
Class representing user.
Definition: User.java:51
ArrayList< Annotation > getAddedAnnotations()
Definition: Flier.java:92
Exception of DocumentUpdate class which is thrown in case of modification failure.
Class representing modification of annotated document text.
String processRequestAfterPersist(RequestInfo requestInfo, boolean persistFailed)
Compare class using Levenshtein approximate string matching method.
Flier with informations for comet handlers.
Definition: Flier.java:31
Class consisting of traversing method and compare method.
Definition: Comparator.java:37
static int getLowestProtocolFromSessionList(RequestInfo requestInfo)
String getQueSubsLike(ArrayList< String > qString)
ArrayList< Annotation > getRemovedAnnotations()
Definition: Flier.java:128
ArrayList< SuggestionLogEntry > getConfirmedSuggestions()
boolean isSubscribed(Annotation annot, boolean testDoc)
Class for matcher consisting of comparator and node iterator.
Definition: Matcher.java:32
Processed informations about client request.
double isAnnotationsAffected(AnnotDocument doc, AnnotDocument newDoc, RequestInfo requestInfo)
Class responsible for localised strings.
static ArrayList< Annotation > getRelatedAnnotations(ArrayList< Annotation > approvedAnnot, ArrayList< Annotation > unapprovedAnnot, EditorSession session)
boolean checkReqAttrsInAnnot(Annotation annot, RequestInfo requestInfo)
ArrayList< Subscription > getNewSubscriptions()
Informations about client session.
void synchronizeLinearizedDocument(AnnotDocument doc, AnnotDocument newDoc, RequestInfo requestInfo)
boolean updateAnnotation(Annotation annotation, RequestInfo requestInfo)
static synchronized boolean lastModificationSetsContains(int documentID, int setID)
Definition: AppBean.java:588
Class representing annotated fragment.
Definition: Fragment.java:48
static synchronized int getDocumentUsage(Integer documentId)
Definition: AppBean.java:465
ArrayList< Annotation > addWithoutDuplicity(ArrayList< Annotation > listOfOld, ArrayList< Annotation > listOfNew)
boolean isSubscribedToLinkingAnnot(EditorSession session, Annotation remAnnot, ArrayList< Annotation > deletedAnnotations)