Back to index

plone3  3.1.7
copier.py
Go to the documentation of this file.
00001 ##################################################################
00002 #
00003 # (C) Copyright 2006-2007 ObjectRealms, LLC
00004 # All Rights Reserved
00005 #
00006 # This file is part of iterate.
00007 #
00008 # iterate is free software; you can redistribute it and/or modify
00009 # it under the terms of the GNU General Public License as published by
00010 # the Free Software Foundation; either version 2 of the License, or
00011 # (at your option) any later version.
00012 #
00013 # iterate is distributed in the hope that it will be useful,
00014 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 # GNU General Public License for more details.
00017 #
00018 # You should have received a copy of the GNU General Public License
00019 # along with CMFDeployment; if not, write to the Free Software
00020 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00021 ##################################################################
00022 
00023 """
00024 $Id: copier.py 1824 2007-02-08 17:59:41Z hazmat $
00025 """
00026 
00027 from zope import interface, component
00028 from zope.annotation.interfaces import IAnnotations
00029 
00030 from Acquisition import aq_base, aq_parent, aq_inner
00031 from ZODB.PersistentMapping import PersistentMapping
00032 
00033 from Products.Archetypes.Referenceable import Referenceable
00034 from Products.CMFCore.utils import getToolByName
00035 from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
00036 
00037 import interfaces
00038 from relation import WorkingCopyRelation
00039 from interfaces import CheckinException
00040 
00041 class ContentCopier( object ):
00042 
00043     interface.implements( interfaces.IObjectCopier )
00044     component.adapts( interfaces.IIterateAware )
00045 
00046     def __init__( self, context ):
00047         self.context = context
00048 
00049     def copyTo( self, container ):
00050         wc = self._copyBaseline( container )
00051         wc_ref = wc.addReference( self.context,
00052                                   relationship = WorkingCopyRelation.relationship,
00053                                   referenceClass = WorkingCopyRelation )
00054         self._handleReferences( self.context, wc, "checkout", wc_ref )
00055         return wc, wc_ref
00056     
00057     def merge( self ):
00058         baseline = self._getBaseline()
00059 
00060         # delete the working copy reference to the baseline
00061         wc_ref = self._deleteWorkingCopyRelation()
00062         
00063         # reassemble references on the new baseline         
00064         self._handleReferences( baseline, self.context, "checkin", wc_ref )
00065 
00066         # move the working copy to the baseline container, deleting the baseline 
00067         new_baseline = self._replaceBaseline( baseline )
00068 
00069         # patch the working copy with baseline info not preserved during checkout
00070         self._reassembleWorkingCopy( new_baseline, baseline )
00071 
00072         return new_baseline
00073 
00074     def _getBaseline( self ):
00075         # follow the working copy's reference back to the baseline
00076         refs = self.context.getRefs( WorkingCopyRelation.relationship )
00077 
00078         if not len(refs) == 1:
00079             raise CheckinException( "Baseline count mismatch" )
00080 
00081         if not refs or refs[0] is None:
00082             raise CheckinException( "Baseline has disappeared" )
00083 
00084         baseline = refs[0]
00085         return baseline
00086 
00087 
00088     def _replaceBaseline( self, baseline ):
00089         # move the working copy object to the baseline, returns the new baseline
00090         baseline_id = baseline.getId()
00091         
00092         # delete the baseline from the folder to make room for the committed working copy
00093         baseline_container = aq_parent( aq_inner( baseline ) )
00094         baseline_container._delOb( baseline_id )
00095 
00096         # delete the working copy from the its container
00097         wc_container =  aq_parent( aq_inner( self.context ) )
00098 
00099         # trick out the at machinery to not delete references
00100         self.context._v_cp_refs = 1
00101         self.context._v_is_cp = 0
00102         
00103         wc_container.manage_delObjects( [self.context.getId()] )
00104         
00105         # move the working copy back to the baseline container
00106         working_copy = aq_base( self.context )
00107         working_copy.setId( baseline_id )
00108         baseline_container._setOb( baseline_id, working_copy )
00109 
00110         new_baseline = baseline_container._getOb( baseline_id )
00111         
00112         # reregister our references with the reference machinery after moving
00113         Referenceable.manage_afterAdd( new_baseline, new_baseline, baseline_container)
00114         
00115         return new_baseline
00116 
00117     def _reassembleWorkingCopy( self, new_baseline, baseline ):
00118         # reattach the source's workflow history, try avoid a dangling ref 
00119         try:
00120             new_baseline.workflow_history = PersistentMapping( baseline.workflow_history.items() )
00121         except AttributeError:
00122             # No workflow apparently.  Oh well.
00123             pass
00124 
00125         # reset wf state security directly
00126         workflow_tool = getToolByName(self.context, 'portal_workflow')
00127         wfs = workflow_tool.getWorkflowsFor( self.context )
00128         for wf in wfs:
00129             if not isinstance( wf, DCWorkflowDefinition ):
00130                 continue
00131             wf.updateRoleMappingsFor( new_baseline )
00132 
00133         # reattach the source's uid, this will update wc refs to point back to the new baseline
00134         new_baseline._setUID( baseline.UID() )
00135 
00136         # reattach the source's history id, to get the previous version ancestry
00137         histid_handler = getToolByName( self.context, 'portal_historyidhandler')
00138         huid = histid_handler.getUid( baseline )
00139         histid_handler.setUid( new_baseline, huid, check_uniqueness=False )
00140 
00141         return new_baseline
00142 
00143     def _deleteWorkingCopyRelation( self ):
00144         # delete the wc reference keeping a reference to it for its annotations
00145         refs = self.context.getReferenceImpl( WorkingCopyRelation.relationship )
00146         wc_ref = refs[0]
00147         self.context.deleteReferences( WorkingCopyRelation.relationship )
00148         return wc_ref
00149 
00150     #################################
00151     ## Checkout Support Methods
00152     
00153     def _copyBaseline( self, container ):
00154         # copy the context from source to the target container
00155         source_container = aq_parent( aq_inner( self.context ) )
00156         clipboard = source_container.manage_copyObjects( [ self.context.getId() ] )
00157         result = container.manage_pasteObjects( clipboard )
00158 
00159         # get a reference to the working copy
00160         target_id = result[0]['new_id']
00161         target = container._getOb( target_id )
00162         return target
00163 
00164     
00165     def _handleReferences( self, baseline, wc, mode, wc_ref ):
00166 
00167         annotations = IAnnotations( wc_ref )
00168         
00169         baseline_adapter = interfaces.ICheckinCheckoutReference( baseline )
00170         
00171         # handle forward references
00172         for relationship in baseline.getRelationships():
00173             # look for a named relation adapter first
00174             adapter = component.queryAdapter( baseline,
00175                                               interfaces.ICheckinCheckoutReference,
00176                                               relationship )
00177             
00178             if adapter is None: # default
00179                 adapter = baseline_adapter
00180                 
00181             references = baseline.getReferenceImpl( relationship )
00182 
00183             mode_method = getattr( adapter, mode )
00184             mode_method( baseline, wc, references, annotations )
00185 
00186         mode = mode + "BackReferences"
00187         
00188         # handle backward references
00189         for relationship in baseline.getBRelationships():
00190             adapter = component.queryAdapter( baseline,
00191                                               interfaces.ICheckinCheckoutReference,
00192                                               relationship )
00193             if adapter is None:
00194                 adapter = baseline_adapter
00195                 
00196             references = baseline.getBackReferenceImpl( relationship )
00197             
00198             mode_method = getattr( adapter, mode )
00199             mode_method( baseline, wc, references, annotations )