1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.internal.objects.classes

File XClassMigratorListener.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

20
56
6
1
224
145
18
0.32
9.33
6
3

Classes

Class Line # Actions
XClassMigratorListener 61 56 0% 18 3
0.963414696.3%
 

Contributing tests

This file is covered by 76 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.internal.objects.classes;
21   
22    import java.util.List;
23   
24    import javax.inject.Inject;
25    import javax.inject.Named;
26    import javax.inject.Provider;
27   
28    import org.slf4j.Logger;
29    import org.xwiki.model.EntityType;
30    import org.xwiki.model.reference.ClassPropertyReference;
31    import org.xwiki.model.reference.DocumentReferenceResolver;
32    import org.xwiki.model.reference.EntityReference;
33    import org.xwiki.model.reference.EntityReferenceSerializer;
34    import org.xwiki.observation.AbstractEventListener;
35    import org.xwiki.observation.event.Event;
36    import org.xwiki.query.Query;
37    import org.xwiki.query.QueryException;
38    import org.xwiki.query.QueryManager;
39   
40    import com.xpn.xwiki.XWikiContext;
41    import com.xpn.xwiki.XWikiException;
42    import com.xpn.xwiki.doc.XWikiDocument;
43    import com.xpn.xwiki.internal.event.AbstractXClassPropertyEvent;
44    import com.xpn.xwiki.internal.event.XClassPropertyAddedEvent;
45    import com.xpn.xwiki.internal.event.XClassPropertyUpdatedEvent;
46    import com.xpn.xwiki.internal.store.PropertyConverter;
47    import com.xpn.xwiki.objects.BaseObject;
48    import com.xpn.xwiki.objects.BaseProperty;
49    import com.xpn.xwiki.objects.classes.PropertyClass;
50   
51    /**
52    * Listen to classes modifications and automatically update objects accordingly when needed.
53    * <p>
54    * The actual conversion is done in {@link PropertyConverter}.
55    *
56    * @version $Id: dcf7501ba942fefe7da37cfa598a9e9ac7e3dd51 $
57    * @since 7.1RC1
58    */
59    // TODO: could be optimized a bit by listening to XClassUpdatedEvent and redoing the comparison between the two
60    // classes in case there is several changes to the class
 
61    public class XClassMigratorListener extends AbstractEventListener
62    {
63    @Inject
64    @Named("local")
65    private EntityReferenceSerializer<String> localSerializer;
66   
67    @Inject
68    private DocumentReferenceResolver<String> resolver;
69   
70    @Inject
71    private QueryManager queryManager;
72   
73    @Inject
74    private Provider<XWikiContext> xcontextProvider;
75   
76    /**
77    * Used for migrating the property values after a class is modified.
78    */
79    @Inject
80    private PropertyConverter propertyConverter;
81   
82    @Inject
83    private Logger logger;
84   
85    /**
86    * Setup the listener.
87    */
 
88  218 toggle public XClassMigratorListener()
89    {
90  218 super(XClassMigratorListener.class.getName(), new XClassPropertyUpdatedEvent(), new XClassPropertyAddedEvent());
91    }
92   
 
93  10094 toggle @Override
94    public void onEvent(Event event, Object source, Object data)
95    {
96  10094 AbstractXClassPropertyEvent propertyEvent = (AbstractXClassPropertyEvent) event;
97  10094 XWikiDocument newDocument = (XWikiDocument) source;
98  10094 XWikiDocument previousDocument = newDocument.getOriginalDocument();
99   
100  10094 PropertyClass newPropertyClass =
101    (PropertyClass) newDocument.getXClass().getField(propertyEvent.getReference().getName());
102  10094 PropertyClass previousPropertyClass =
103    (PropertyClass) previousDocument.getXClass().getField(propertyEvent.getReference().getName());
104   
105  10094 boolean migrate = false;
106  10094 if (newPropertyClass != null) {
107  10092 BaseProperty<?> newProperty = newPropertyClass.newProperty();
108   
109  10092 if (previousPropertyClass != null) {
110  472 BaseProperty<?> previousProperty = previousPropertyClass.newProperty();
111   
112    // New and previous class property generate different kind of properties
113  472 migrate = newProperty.getClass() != previousProperty.getClass();
114    } else {
115  9620 migrate = true;
116    }
117   
118  10092 if (migrate) {
119  9639 try {
120  9639 migrate(newPropertyClass, newProperty);
121    } catch (QueryException e) {
122  0 this.logger.error("Failed to migrate XClass property [{}]", newPropertyClass.getReference(), e);
123    }
124    }
125    }
126    }
127   
 
128  9639 toggle private void migrate(PropertyClass newPropertyClass, BaseProperty<?> newProperty) throws QueryException
129    {
130  9639 ClassPropertyReference propertyReference = newPropertyClass.getReference();
131  9639 EntityReference classReference = propertyReference.extractReference(EntityType.DOCUMENT);
132  9639 EntityReference wikiReference = propertyReference.extractReference(EntityType.WIKI);
133   
134    // Get all the documents containing at least one object of the modified class
135  9639 Query query = this.queryManager
136    .createQuery("select distinct obj.name from BaseObject as obj where obj.className = ?", Query.HQL);
137  9639 query.bindValue(0, this.localSerializer.serialize(classReference));
138  9639 query.setWiki(wikiReference.getName());
139   
140  9639 List<String> documents = query.execute();
141   
142  9639 if (!documents.isEmpty()) {
143  844 XWikiContext xcontext = this.xcontextProvider.get();
144   
145  844 String currentWikiId = xcontext.getWikiId();
146  844 try {
147    // Switch to class wiki to be safer
148  844 xcontext.setWikiId(wikiReference.getName());
149   
150  844 for (String documentName : documents) {
151  2343 try {
152  2343 migrate(newPropertyClass, newProperty, documentName, xcontext);
153    } catch (XWikiException e) {
154  0 this.logger.error("Failed to migrate property [{}] in document [{}]", propertyReference,
155    documentName);
156    }
157    }
158    } finally {
159    // Restore context wiki
160  844 xcontext.setWikiId(currentWikiId);
161    }
162    }
163    }
164   
 
165  2343 toggle private void migrate(PropertyClass newPropertyClass, BaseProperty<?> newProperty, String documentName,
166    XWikiContext xcontext) throws XWikiException
167    {
168  2343 ClassPropertyReference propertyReference = newPropertyClass.getReference();
169  2343 EntityReference classReference = propertyReference.extractReference(EntityType.DOCUMENT);
170   
171  2343 XWikiDocument document =
172    xcontext.getWiki().getDocument(this.resolver.resolve(documentName, classReference), xcontext);
173   
174  2343 if (!document.isNew()) {
175  2342 boolean modified = false;
176   
177  2342 for (BaseObject xobject : document.getXObjects(classReference)) {
178  2725 if (xobject != null) {
179  2646 BaseProperty<?> property = (BaseProperty<?>) xobject.getField(propertyReference.getName());
180   
181    // If the existing field is of different kind than what is produced by the new class property
182  2646 if (property != null) {
183  2203 modified = convert(xobject, property, newProperty, newPropertyClass);
184    } else {
185  443 modified = add(xobject, newPropertyClass);
186    }
187    }
188    }
189   
190    // If anything changed save the document
191  2342 if (modified) {
192  302 xcontext.getWiki().saveDocument(document, "Migrated property [" + propertyReference.getName()
193    + "] from class [" + this.localSerializer.serialize(classReference) + "]", xcontext);
194    }
195    }
196    }
197   
 
198  443 toggle private boolean add(BaseObject xobject, PropertyClass newPropertyClass)
199    {
200  443 xobject.safeput(newPropertyClass.getName(), newPropertyClass.newProperty());
201   
202  443 return true;
203    }
204   
 
205  2203 toggle private boolean convert(BaseObject xobject, BaseProperty<?> property, BaseProperty<?> newProperty,
206    PropertyClass newPropertyClass)
207    {
208  2203 if (property.getClass() != newProperty.getClass()) {
209  16 BaseProperty<?> convertedProperty = this.propertyConverter.convertProperty(property, newPropertyClass);
210   
211    // Set new field
212  16 if (convertedProperty != null) {
213    // Mark old field for removal, only if the conversion was successful, to avoid losing data.
214  16 xobject.removeField(newPropertyClass.getName());
215   
216  16 xobject.safeput(newPropertyClass.getName(), convertedProperty);
217   
218  16 return true;
219    }
220    }
221   
222  2187 return false;
223    }
224    }