View Javadoc
1   /*
2    * Copyright (C) 2003-2010 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.wiki.mow.core.api.wiki;
18  
19  import org.chromattic.api.ChromatticSession;
20  import org.chromattic.api.DuplicateNameException;
21  import org.chromattic.api.RelationshipType;
22  import org.chromattic.api.annotations.*;
23  import org.chromattic.api.annotations.Property;
24  import org.chromattic.ext.ntdef.NTFolder;
25  import org.chromattic.ext.ntdef.Resource;
26  import org.exoplatform.portal.config.model.PortalConfig;
27  import org.exoplatform.services.jcr.RepositoryService;
28  import org.exoplatform.services.jcr.ext.common.SessionProvider;
29  import org.exoplatform.services.log.ExoLogger;
30  import org.exoplatform.services.log.Log;
31  import org.exoplatform.services.security.ConversationState;
32  import org.exoplatform.services.security.Identity;
33  import org.exoplatform.wiki.WikiException;
34  import org.exoplatform.wiki.chromattic.ext.ntdef.NTVersion;
35  import org.exoplatform.wiki.chromattic.ext.ntdef.UncachedMixin;
36  import org.exoplatform.wiki.chromattic.ext.ntdef.VersionableMixin;
37  import org.exoplatform.wiki.mow.api.Permission;
38  import org.exoplatform.wiki.mow.core.api.MOWService;
39  import org.exoplatform.wiki.rendering.converter.ConfluenceToXWiki2Transformer;
40  import org.exoplatform.wiki.rendering.util.Utils;
41  import org.exoplatform.wiki.resolver.TitleResolver;
42  import org.exoplatform.wiki.mow.api.PermissionType;
43  import org.exoplatform.wiki.utils.WikiConstants;
44  import org.xwiki.component.manager.ComponentManager;
45  import org.xwiki.rendering.syntax.Syntax;
46  
47  import javax.jcr.*;
48  import javax.jcr.query.Query;
49  import javax.jcr.version.Version;
50  import javax.jcr.version.VersionIterator;
51  import java.util.*;
52  import java.util.Map.Entry;
53  
54  @PrimaryType(name = WikiNodeType.WIKI_PAGE)
55  public abstract class PageImpl extends NTFolder {
56    
57    private static final Log      LOG               = ExoLogger.getLogger(PageImpl.class.getName());
58    
59    private MOWService mowService;
60    
61    private PermissionImpl permission = new PermissionImpl();
62    
63    private ComponentManager componentManager;
64    
65    /**
66     * caching related pages for performance
67     */
68    private List<PageImpl> relatedPages = null;
69    
70    private boolean isMinorEdit = false;
71    
72    public String getID() throws RepositoryException {
73      return this.getJCRPageNode().getUUID();
74    }
75    public void setMOWService(MOWService mowService) {
76      this.mowService = mowService;
77      permission.setMOWService(mowService);
78    }
79    
80    public MOWService getMOWService() {
81      return mowService;
82    }
83  
84    public ChromatticSession getChromatticSession() {
85      return mowService.getSession();
86    }
87    
88    public Session getJCRSession() {
89      return getChromatticSession().getJCRSession();
90    }
91    
92    public void setComponentManager(ComponentManager componentManager) {
93      this.componentManager = componentManager;
94    }
95  
96    public Node getJCRPageNode() throws RepositoryException {
97      return (Node) getChromatticSession().getJCRSession().getItem(getPath());
98    }
99    
100   @Name
101   @Override
102   public abstract String getName();
103   public abstract void setName(String name);
104   
105   @Path
106   public abstract String getPath();
107 
108   @WorkspaceName
109   public abstract String getWorkspace();
110   
111   @OneToOne
112   @Owner
113   @MappedBy(WikiNodeType.Definition.CONTENT)
114   protected abstract AttachmentImpl getContentByChromattic();
115 
116   protected abstract void setContentByChromattic(AttachmentImpl content);
117 
118   @Create
119   protected abstract AttachmentImpl createContent();
120 
121   public AttachmentImpl getContent() {
122     AttachmentImpl content = getContentByChromattic();
123     if (content == null) {
124       content = createContent();
125       setContentByChromattic(content);
126       content.setText("");
127     } else {
128       String syntax = getSyntax();
129       if (Syntax.CONFLUENCE_1_0.toIdString().equals(syntax)) {
130         content.setText(ConfluenceToXWiki2Transformer.transformContent(content.getText(), componentManager));
131         setSyntax(Syntax.XWIKI_2_0.toIdString());
132         setContentByChromattic(content);
133       }
134     }
135     return content;
136   }
137   
138   @Property(name = WikiNodeType.Definition.TITLE)
139   public abstract String getTitleByChromattic();
140   public abstract void setTitleByChromattic(String title);
141   
142   public String getTitle() {
143     String title = getTitleByChromattic();
144     return (title != null) ? title : getName();
145   }
146   
147   public void setTitle(String title) {
148     setTitleByChromattic(title);
149   }
150   
151   @Property(name = WikiNodeType.Definition.SYNTAX)
152   public abstract String getSyntax();
153   public abstract void setSyntax(String syntax);
154   
155   @Property(name = WikiNodeType.Definition.COMMENT)
156   @DefaultValue({""})
157   public abstract String getComment();
158   public abstract void setComment(String comment);
159   
160   @Property(name = WikiNodeType.Definition.OWNER)
161   public abstract String getOwner();
162   public abstract void setOwner(String owner);
163   
164   @Property(name = WikiNodeType.Definition.AUTHOR)
165   public abstract String getAuthor();
166   public abstract void setAuthor(String author);
167 
168   @Property(name = WikiNodeType.Definition.CREATED_DATE)
169   public abstract Date getCreatedDate();
170   public abstract void setCreatedDate(Date date);
171   
172   @Property(name = WikiNodeType.Definition.UPDATED_DATE)
173   public abstract Date getUpdatedDate();
174   public abstract void setUpdatedDate(Date date);
175   
176   @Property(name = WikiNodeType.Definition.URL)
177   public abstract String getURL();
178   public abstract void setURL(String url);
179 
180   @OneToOne(type = RelationshipType.EMBEDDED)
181   @Owner
182   public abstract ActivityInfoMixin getActivityInfoMixin();
183   public abstract void setActivityInfoMixin(ActivityInfoMixin activity);
184   
185   @OneToOne(type = RelationshipType.EMBEDDED)
186   @Owner
187   public abstract MovedMixin getMovedMixin();
188   public abstract void setMovedMixin(MovedMixin move);
189   
190   @OneToOne(type = RelationshipType.EMBEDDED)
191   @Owner
192   public abstract RemovedMixin getRemovedMixin();
193   public abstract void setRemovedMixin(RemovedMixin remove);
194   
195   @OneToOne(type = RelationshipType.EMBEDDED)
196   @Owner
197   public abstract RenamedMixin getRenamedMixin();
198   public abstract void setRenamedMixin(RenamedMixin mix);
199 
200   @OneToOne(type = RelationshipType.EMBEDDED)
201   @Owner
202   public abstract UncachedMixin getUncachedMixin();
203   public abstract void setUncachedMixin(UncachedMixin mix);
204   
205   @OneToOne(type = RelationshipType.EMBEDDED)
206   @Owner
207   public abstract WatchedMixin getWatchedMixin();
208   public abstract void setWatchedMixin(WatchedMixin mix);
209   
210   @Create
211   protected abstract WatchedMixin createWatchedMixin();
212   
213   @OneToOne(type = RelationshipType.EMBEDDED)
214   @Owner
215   public abstract MigratingMixin getMigratingMixin();
216   public abstract void setMigratingMixin(MigratingMixin mix);
217 
218   @Create
219   protected abstract MigratingMixin createMigratingMixin();
220   
221   public void makeWatched() {
222     WatchedMixin watchedMixin = getWatchedMixin();
223     if (watchedMixin == null) {
224       watchedMixin = createWatchedMixin();
225       setWatchedMixin(watchedMixin);
226     }
227   }
228   
229   public VersionableMixin getVersionableMixin() {
230     try {
231       migrateLegacyData();
232     } catch (Exception e) {
233       if (LOG.isWarnEnabled()) {
234         LOG.warn("Can not migrate legacy version data", e.getMessage());
235       }
236     }
237     return getContent().getVersionableMixin();
238   }
239 
240   public void makeVersionable() {
241     this.getContent().makeVersionable();
242   }
243   
244   
245   //TODO: replace by @Checkin when Chromattic support
246   public NTVersion checkin() throws RepositoryException {
247     PageDescriptionMixin description = getContent().getPageDescriptionMixin();
248     description.setAuthor(ConversationState.getCurrent().getIdentity().getUserId());
249     description.setUpdatedDate(GregorianCalendar.getInstance().getTime());
250     description.setComment(this.getComment());
251     //create new version only for the page content node, but whole wiki page to improve performance.
252     NTVersion ret = getContent().checkin();
253     return ret;
254   }
255 
256   //TODO: replace by @Checkout when Chromattic support
257   public void checkout() throws RepositoryException {
258     getContent().checkout();
259   }
260 
261   //TODO: replace by @Restore when Chromattic support
262   public void restore(String versionName, boolean removeExisting) throws WikiException {
263     getContent().restore(versionName, removeExisting);
264   }
265   
266   @Create
267   public abstract AttachmentImpl createAttachment();
268   
269   public AttachmentImpl createAttachment(String fileName, Resource contentResource) throws WikiException {
270     if (fileName == null) {
271       throw new NullPointerException();
272     }
273     Iterator<AttachmentImpl> attIter= getAttachments().iterator();
274     while (attIter.hasNext()) {
275       AttachmentImpl att = attIter.next();
276       if (att.getName().equals(fileName)) {
277         att.remove();
278       }
279     }
280     
281     AttachmentImpl file = createAttachment();
282     file.setName(TitleResolver.getId(fileName, false));
283     addAttachment(file);
284     if (fileName.lastIndexOf(".") > 0) {
285       file.setTitle(fileName.substring(0, fileName.lastIndexOf(".")));
286       file.setFileType(fileName.substring(fileName.lastIndexOf(".")));
287     } else {
288       file.setTitle(fileName);
289     }
290     
291     if (contentResource != null) {
292       file.setContentResource(contentResource);
293     }
294     getChromatticSession().save();
295     setFullPermissionForOwner(file);
296     return file;
297   }
298   
299   private void setFullPermissionForOwner(AttachmentImpl file) throws WikiException {
300     ConversationState conversationState = ConversationState.getCurrent();
301 
302     if (conversationState != null) {
303       HashMap<String, String[]> permissions = file.getPermission();
304       permissions.put(conversationState.getIdentity().getUserId(), org.exoplatform.services.jcr.access.PermissionType.ALL);
305       file.setPermission(permissions);
306     }
307   }
308   
309   @OneToMany
310   public abstract Collection<AttachmentImpl> getAttachmentsByChromattic();
311 
312   public Collection<AttachmentImpl> getAttachments() {
313     return getAttachmentsByChromattic();
314   }
315   
316   public Collection<AttachmentImpl> getAttachmentsExcludeContent() throws RepositoryException {
317     List<AttachmentImpl> atts = new ArrayList<AttachmentImpl>();
318     String path = this.getPath();
319     StringBuilder statement = new StringBuilder("SELECT * FROM ");
320     statement.append(WikiNodeType.WIKI_ATTACHMENT)
321              .append(" WHERE jcr:path LIKE '").append(path)
322              .append("/%' AND NOT jcr:path LIKE '").append(path).append("/%/%'");
323     NodeIterator iter = getChromatticSession().getJCRSession().getWorkspace()
324       .getQueryManager().createQuery(statement.toString(), Query.SQL)
325       .execute().getNodes();
326     while (iter.hasNext()) {
327       Node node = iter.nextNode();
328       AttachmentImpl att = this.getChromatticSession().findByNode(AttachmentImpl.class, node);
329       if ((att.hasPermission(PermissionType.VIEW_ATTACHMENT)
330           || att.hasPermission(PermissionType.EDIT_ATTACHMENT))
331           && !WikiNodeType.Definition.CONTENT.equals(att.getName())) {
332         atts.add(att);
333       }
334     }
335     Collections.sort(atts);
336     return atts;
337   }
338   
339   public Collection<AttachmentImpl> getAttachmentsExcludeContentByRootPermisison() {
340     Collection<AttachmentImpl> attachments = getAttachmentsByChromattic();
341     List<AttachmentImpl> atts = new ArrayList<AttachmentImpl>(attachments.size());
342     for (AttachmentImpl attachment : attachments) {
343       if (!WikiNodeType.Definition.CONTENT.equals(attachment.getName())) {
344         atts.add(attachment);
345       }
346     }
347     Collections.sort(atts);
348     return atts;
349   }
350   
351   public AttachmentImpl getAttachment(String attachmentId) {
352     for (AttachmentImpl attachment : getAttachments()) {
353       if (attachment.getName().equals(attachmentId)
354           && (attachment.hasPermission(PermissionType.VIEW_ATTACHMENT)
355           || attachment.hasPermission(PermissionType.EDIT_ATTACHMENT))) {
356         return attachment;
357       }
358     }
359     return null;
360   }
361   
362   public AttachmentImpl getAttachmentByRootPermisison(String attachmentId) throws Exception {
363     for (AttachmentImpl attachment : getAttachments()) {
364       if (attachment.getName().equals(attachmentId)) {
365         return attachment;
366       }
367     }
368     return null;
369   }
370   
371   public void addAttachment(AttachmentImpl attachment) throws DuplicateNameException {
372     getAttachments().add(attachment);
373   }  
374   
375   public void removeAttachment(String attachmentId) {
376     AttachmentImpl attachment = getAttachment(attachmentId);
377     if(attachment != null){
378       attachment.remove();
379     }
380   }
381   
382   @ManyToOne
383   public abstract PageImpl getParentPage();
384   public abstract void setParentPage(PageImpl page);
385 
386   @ManyToOne
387   public abstract Trash getTrash();
388   public abstract void setTrash(Trash trash);
389   
390   @OneToMany
391   protected abstract Map<String, PageImpl> getChildrenContainer();
392   
393   public Map<String, PageImpl> getChildPages() {
394     TreeMap<String, PageImpl> result = new TreeMap<>(new Comparator<String>() {
395       @Override
396       public int compare(String o1, String o2) {
397         return o1.compareTo(o2);
398       }
399     });
400     List<PageImpl> pages = new ArrayList<PageImpl>(getChildrenContainer().values());
401     
402     for (int i = 0; i < pages.size(); i++) {
403       PageImpl page = pages.get(i);
404       if (page != null && page.hasPermission(PermissionType.VIEWPAGE)) {
405         result.put(page.getName(), page);
406       }
407     }
408     return result;
409   }
410   
411   public Map<String, PageImpl> getChildrenByRootPermission() {
412     TreeMap<String, PageImpl> result = new TreeMap<String, PageImpl>(new Comparator<String>() {
413       @Override
414       public int compare(String o1, String o2) {
415         return o1.compareTo(o2);
416       }
417     });
418     List<PageImpl> pages = new ArrayList<PageImpl>(getChildrenContainer().values());
419     
420     for (int i = 0; i < pages.size(); i++) {
421       PageImpl page = pages.get(i);
422       if (page != null) {
423         result.put(page.getTitle(), page);
424       }
425     }
426     return result;
427   }
428   
429   @Property(name = WikiNodeType.Definition.OVERRIDEPERMISSION)
430   public abstract boolean getOverridePermission();
431   public abstract void setOverridePermission(boolean isOverridePermission);
432   
433   public boolean hasPermission(PermissionType permissionType) {
434     return permission.hasPermission(permissionType, getPath());
435   }
436   
437   public boolean hasPermission(PermissionType permissionType, Identity user) {
438     return permission.hasPermission(permissionType, getPath(), user);
439   }
440   
441   public HashMap<String, String[]> getPermission() throws WikiException {
442     return permission.getPermission(getPath());
443   }
444   
445   public void setPermission(HashMap<String, String[]> permissions) throws WikiException {
446     permission.setPermission(permissions, getPath());
447   }
448   
449   public void setNonePermission() throws WikiException {
450     setPermission(null);
451   }
452 
453   public String getActivityId() {
454     String activityId = null;
455     ActivityInfoMixin activityInfoMixin = this.getActivityInfoMixin();
456     if(activityInfoMixin != null) {
457       activityId = activityInfoMixin.getActivityId();
458     }
459     return activityId;
460   }
461 
462   public void setActivityId(String activityId) {
463     ActivityInfoMixin activityInfoMixin = this.getActivityInfoMixin();
464     if(activityInfoMixin == null) {
465       ChromatticSession session = mowService.getSession();
466       this.setActivityInfoMixin(session.create(ActivityInfoMixin.class));
467       ActivityInfoMixin mixin = this.getActivityInfoMixin();
468       mixin.setActivityId(activityId);
469     } else if(!activityId.equals(activityInfoMixin.getActivityId())) {
470       ActivityInfoMixin mixin = this.getActivityInfoMixin();
471       mixin.setActivityId(activityId);
472     }
473   }
474   
475   protected void addPage(String pageName, PageImpl page) {
476     Map<String, PageImpl> children = getChildrenContainer();
477     if (children.containsKey(pageName)) {
478       throw new IllegalStateException("Page with name " + pageName + " already exists");
479     }
480     children.put(pageName, page);
481   }
482   
483   public void addWikiPage(PageImpl page) {
484     addPage(page.getName(), page);
485   }
486   
487   public void addPublicPage(PageImpl page) throws WikiException {
488     addWikiPage(page);
489     page.setNonePermission();
490   }
491   
492   public PageImpl getWikiPage(String pageId) throws Exception{
493     if(WikiConstants.WIKI_HOME_NAME.equalsIgnoreCase(pageId)){
494       return this;
495     }
496     Iterator<PageImpl> iter = getChildPages().values().iterator();
497     while(iter.hasNext()) {
498       PageImpl page = (PageImpl)iter.next() ;
499       if (pageId.equals(page.getName()))  return page ;         
500     }
501     return null ;
502   }
503   
504   public WikiImpl getWiki() {
505     WikiHome wikiHome = getWikiHome();
506     if (wikiHome != null) {
507       PortalWiki portalWiki = wikiHome.getPortalWiki();
508       GroupWiki groupWiki = wikiHome.getGroupWiki();
509       UserWiki userWiki = wikiHome.getUserWiki();
510       if (portalWiki != null) {
511         return portalWiki;
512       } else if (groupWiki != null) {
513         return groupWiki;
514       } else {
515         return userWiki;
516       }
517     }
518     return null;
519   }
520 
521   public WikiHome getWikiHome() {
522     PageImpl parent = this.getParentPage();
523     if (this instanceof WikiHome) {
524       parent = this;
525     } else
526       while (parent != null && !(parent instanceof WikiHome)) {
527         parent = parent.getParentPage();
528       }
529     return (WikiHome) parent;
530   }
531   
532   public boolean isMinorEdit() {
533     return isMinorEdit;
534   }
535 
536   public void setMinorEdit(boolean isMinorEdit) {
537     this.isMinorEdit = isMinorEdit;
538   }
539 
540   @Destroy
541   public abstract void remove();
542   
543   /**
544    * add a related page
545    * @param page
546    * @return uuid of node of related page if add successfully. <br>
547    *         null if add failed.
548    * @throws NullPointerException if the param is null
549    * @throws RepositoryException when any error occurs.
550    */
551   public synchronized String addRelatedPage(PageImpl page) throws RepositoryException {
552     Map<String, Value> referredUUIDs = getReferredUUIDs();
553     Session jcrSession = getJCRSession();
554     Node myJcrNode = (Node) jcrSession.getItem(getPath());
555     Node referredJcrNode = (Node) jcrSession.getItem(page.getPath());
556     String referedUUID = referredJcrNode.getUUID();
557     if (referredUUIDs.containsKey(referedUUID)) {
558       return null;
559     }
560     Value value2Add = jcrSession.getValueFactory().createValue(referredJcrNode);
561     referredUUIDs.put(referedUUID, value2Add);
562 
563     myJcrNode.setProperty(WikiNodeType.Definition.RELATION,
564                           referredUUIDs.values().toArray(new Value[referredUUIDs.size()]));
565     myJcrNode.save();
566     // cache a related page.
567     if (relatedPages != null) relatedPages.add(page);
568     return referedUUID;
569   }
570   
571   public List<PageImpl> getRelatedPages() throws RepositoryException {
572     if (relatedPages == null) {
573       relatedPages = new ArrayList<>();
574       Iterator<Entry<String, Value>> refferedIter = getReferredUUIDs().entrySet().iterator();
575       ChromatticSession chSession = getChromatticSession();
576       while (refferedIter.hasNext()) {
577         Entry<String, Value> entry = refferedIter.next();
578         PageImpl page = chSession.findById(PageImpl.class, entry.getValue().getString());
579         if(page != null && page.hasPermission(PermissionType.VIEWPAGE)){
580           relatedPages.add(page);
581         }
582       }
583     }
584     return new ArrayList<>(relatedPages);
585   }
586   
587   /**
588    * remove a specified related page.
589    * @param page
590    * @return uuid of node if related page is removed successfully <br>
591    *         null if removing failed.
592    * @throws RepositoryException when an error is thrown.
593    */
594   public synchronized String removeRelatedPage(PageImpl page) throws RepositoryException {
595     Map<String, Value> referedUUIDs = getReferredUUIDs();
596     Session jcrSession = getJCRSession();
597     Node referredJcrNode = (Node) jcrSession.getItem(page.getPath());
598     Node myJcrNode = (Node) jcrSession.getItem(getPath());
599     String referredUUID = referredJcrNode.getUUID();
600     if (!referedUUIDs.containsKey(referredUUID)) {
601       return null;
602     }
603     referedUUIDs.remove(referredUUID);
604     myJcrNode.setProperty(WikiNodeType.Definition.RELATION,
605                           referedUUIDs.values().toArray(new Value[referedUUIDs.size()]));
606     myJcrNode.save();
607     // remove page from cache
608     if (relatedPages != null) relatedPages.remove(page);
609     return referredUUID;
610   }
611 
612   
613   /**
614    * get reference uuids of current page
615    * @return {@code Map<String, Value>} map of referred uuids of current page
616    * @throws RepositoryException when an error is thrown.
617    */
618   public Map<String, Value> getReferredUUIDs() throws RepositoryException {
619     Session jcrSession = getJCRSession();
620     Node myJcrNode = (Node) jcrSession.getItem(getPath());
621     Map<String, Value> referedUUIDs = new HashMap<String, Value>();
622     if (myJcrNode.hasProperty(WikiNodeType.Definition.RELATION)) {
623       Value[] values = myJcrNode.getProperty(WikiNodeType.Definition.RELATION).getValues();
624       if (values != null && values.length > 0) {
625         for (Value value : values) {
626           referedUUIDs.put(value.getString(), value);
627         }
628       }
629     }
630     return referedUUIDs;
631   }
632   
633   public synchronized void removeAllRelatedPages() throws Exception {
634     Session jcrSession = getJCRSession();
635     Node myJcrNode = (Node) jcrSession.getItem(getPath());
636     myJcrNode.setProperty(WikiNodeType.Definition.RELATION, (Value[]) null);
637     myJcrNode.save();
638     // clear related pages in cache.
639     if (relatedPages != null) relatedPages.clear();
640   }
641   
642   /**
643    * Migrates old page history data on the fly: 1.create version history of content child node <br>
644    * based on the history of page node, 2.remove the mix:versionable from page node. 
645    * @throws RepositoryException
646    */
647   public void migrateLegacyData() throws RepositoryException {
648     //migrate only when the current Page Node is mix:versionable
649     if (this.getJCRPageNode().isNodeType(WikiNodeType.MIX_VERSIONABLE) && 
650         (this.getContent().getVersionableMixinByChromattic() == null)) {
651       Node pageNode = this.getJCRPageNode();
652       if (LOG.isInfoEnabled()) {
653         LOG.info("Migrating history for wiki page: " + pageNode.getPath());
654       }
655       //get history: author list, content list and updatedDate list
656       List<VersionData> versions = new ArrayList<VersionData>();
657       VersionIterator iter = pageNode.getVersionHistory().getAllVersions();
658       while (iter.hasNext()) {
659         Version v = iter.nextVersion();
660         if (v.hasNode(WikiNodeType.JCR_FROZEN_NODE))  {
661           String name = v.getName();
662           Node frozenNode = v.getNode(WikiNodeType.JCR_FROZEN_NODE);
663           String author = (frozenNode.hasProperty(WikiNodeType.Definition.AUTHOR) ?
664                               frozenNode.getProperty(WikiNodeType.Definition.AUTHOR).getString() : "");
665           Calendar calendar = (frozenNode.hasProperty(WikiNodeType.Definition.UPDATED_DATE) ? 
666                                 frozenNode.getProperty(WikiNodeType.Definition.UPDATED_DATE).getDate() : 
667                         GregorianCalendar.getInstance());
668           String content = (frozenNode.getNode(WikiNodeType.Definition.CONTENT)
669                                       .getNode(WikiNodeType.Definition.ATTACHMENT_CONTENT)
670                                       .getProperty(WikiNodeType.Definition.DATA).getString());
671           String comment = (frozenNode.hasProperty(WikiNodeType.Definition.COMMENT) ?
672                                  frozenNode.getProperty(WikiNodeType.Definition.COMMENT).getString() : "");
673           versions.add(new VersionData(name, author, calendar, content, comment));
674         }
675       }
676       Collections.sort(versions);
677       //add mix wiki:migrating to avoid sending email when migrating
678       MigratingMixin migrateMix = this.createMigratingMixin();
679       this.setMigratingMixin(migrateMix);
680       
681       this.makeVersionable();
682       AttachmentImpl content = this.getContent();
683       //save the current content
684       String currentContent = content.getText();
685       //create version history for content node
686       for (int i = 0; i < versions.size(); i++) {
687         if ((i % 10 == 0) && LOG.isInfoEnabled()) {
688           LOG.info("Creating new version: " + i);
689         }
690         PageDescriptionMixin description = content.getPageDescriptionMixin();
691         description.setAuthor(versions.get(i).getAuthor());
692         description.setUpdatedDate(versions.get(i).getCalendar().getTime());
693         content.setText(versions.get(i).getContent());
694         description.setComment(versions.get(i).getComment());
695         content.checkin();
696         content.checkout();
697       }
698       //restore the current content
699       content.setText(currentContent);
700       this.getChromatticSession().setEmbedded(this, MigratingMixin.class, null);
701       pageNode.save();
702       //remove mix:versionable of the page itself
703       removeMixVersionable(pageNode);
704     }
705   }
706   
707   @OneToOne(type = RelationshipType.EMBEDDED)
708   @Owner
709   public abstract UpdateAttachmentMixin getUpdateAttachmentMixin();
710 
711   public abstract void setUpdateAttachmentMixin(UpdateAttachmentMixin mix);
712 
713   @Create
714   public abstract UpdateAttachmentMixin createUpdateAttachmentMixin();
715 
716   /* Grant read permission for any for all attachments */
717   public void migrateAttachmentPermission() throws WikiException {
718 
719     boolean isGroupWiki = PortalConfig.GROUP_TYPE.equals(this.getWiki().getType());
720     UpdateAttachmentMixin updateAttachment = this.getUpdateAttachmentMixin();
721     if (updateAttachment == null) {
722       Collection<AttachmentImpl> attachments = this.getAttachmentsExcludeContentByRootPermisison();
723       Set<String> permissionKeys = new HashSet<String> (this.getPermission().keySet());
724       for (AttachmentImpl attachment : attachments) {
725         HashMap<String, String[]> permissions = attachment.getPermission();
726         Iterator<Entry<String, String[]>> permissionIterator = permissions.entrySet().iterator();
727         while (permissionIterator.hasNext()) {
728           Entry<String, String[]> attachmentPermissionEntry = permissionIterator.next();
729           String attachmentPermissionKey = attachmentPermissionEntry.getKey();
730           if (permissionKeys.contains(attachmentPermissionKey)) {
731             permissionKeys.remove(attachmentPermissionKey);
732           } else {
733             permissionIterator.remove();
734           }
735         }
736         for (String permissionEntry : permissionKeys) {
737           if (isGroupWiki && permissionEntry.contains(this.getWiki().getOwner())) {
738             permissions.put(permissionEntry, org.exoplatform.services.jcr.access.PermissionType.ALL);
739           } else {
740             permissions.put(permissionEntry, new String[] {org.exoplatform.services.jcr.access.PermissionType.READ});
741           }
742         }
743         attachment.setPermission(permissions);
744       }
745       updateAttachment = this.createUpdateAttachmentMixin();
746       this.setUpdateAttachmentMixin(updateAttachment);
747     }
748   }
749   
750   private void removeMixVersionable(Node node) {
751     (new Thread(new RemoveMixVersionable(node))).start();    
752   }
753   
754   public class RemoveMixVersionable implements Runnable {
755     private String ws;
756     private String nodePath;
757     
758     public RemoveMixVersionable(Node node) {
759       try {
760         ws = node.getSession().getWorkspace().getName();
761         nodePath = node.getPath();
762       } catch (RepositoryException e) {
763         ws = "";
764         nodePath = "";
765       }
766     }
767 
768     public void run() {
769       if (LOG.isInfoEnabled()) {
770         LOG.info("Removing " + WikiNodeType.MIX_VERSIONABLE + " from " + ws + ": " + nodePath);
771       }
772       SessionProvider provider = SessionProvider.createSystemProvider();
773       try {
774         Session session = provider.getSession(ws, Utils.getService(RepositoryService.class).getCurrentRepository());
775         Node node = (Node)session.getItem(nodePath);
776         node.removeMixin(WikiNodeType.MIX_VERSIONABLE);
777         node.save();
778         if (LOG.isInfoEnabled()) {
779           LOG.info(WikiNodeType.MIX_VERSIONABLE + " was removed from " + ws + ": " + nodePath);
780         }
781       } catch (LoginException e) {
782         if (LOG.isWarnEnabled()) {
783           LOG.warn(WikiNodeType.MIX_VERSIONABLE + " cannot be removed from " + ws + ": " + nodePath);
784         }
785       } catch (NoSuchWorkspaceException e) {
786         if (LOG.isWarnEnabled()) {
787           LOG.warn(WikiNodeType.MIX_VERSIONABLE + " cannot be removed from " + ws + ": " + nodePath);
788         }
789       } catch (RepositoryException e) {
790         if (LOG.isWarnEnabled()) {
791           LOG.warn(WikiNodeType.MIX_VERSIONABLE + " cannot be removed from " + ws + ": " + nodePath);
792         }
793       }
794       provider.close();
795     }
796 
797 
798 }
799   
800   public class VersionData implements Comparable<VersionData>{
801     private String name;
802     private String author;
803     private Calendar calendar;
804     private String content;
805     private String comment;
806     
807     public String getName() { return name; }
808 
809     public void setName(String name) { this.name = name; }
810 
811     public String getAuthor() { return author; }
812 
813     public void setAuthor(String author) { this.author = author; }
814 
815     public Calendar getCalendar() { return calendar; }
816 
817     public void setCalendar(Calendar calendar) { this.calendar = calendar; }
818 
819     public String getContent() { return content; }
820 
821     public void setContent(String content) { this.content = content; }
822 
823     public String getComment() { return comment; }
824 
825     public void setComment(String comment) { this.comment = comment; }
826 
827     
828     public VersionData(String name, String author, Calendar calendar, String content, String comment) {
829       this.name = name;
830       this.author = author;
831       this.calendar = calendar;
832       this.content = content;
833       this.comment = comment;
834     }
835 
836     @Override
837     public int compareTo(VersionData arg0) {
838       return name.length() != arg0.name.length() ? 
839                           new Integer(name.length()).compareTo(arg0.name.length()) :
840                           name.compareTo(arg0.name);
841     }
842 
843   }
844 }