1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.localization.wiki.internal

File DocumentTranslationBundleFactory.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

22
115
22
1
499
340
47
0.41
5.23
22
2.14

Classes

Class Line # Actions
DocumentTranslationBundleFactory 86 115 0% 47 28
0.823899482.4%
 

Contributing tests

This file is covered by 3 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 org.xwiki.localization.wiki.internal;
21   
22    import java.util.Arrays;
23    import java.util.List;
24   
25    import javax.inject.Inject;
26    import javax.inject.Named;
27    import javax.inject.Provider;
28    import javax.inject.Singleton;
29   
30    import org.apache.commons.lang3.EnumUtils;
31    import org.slf4j.Logger;
32    import org.xwiki.bridge.event.DocumentCreatedEvent;
33    import org.xwiki.bridge.event.DocumentDeletedEvent;
34    import org.xwiki.bridge.event.DocumentUpdatedEvent;
35    import org.xwiki.bridge.event.WikiReadyEvent;
36    import org.xwiki.cache.Cache;
37    import org.xwiki.cache.CacheException;
38    import org.xwiki.cache.CacheManager;
39    import org.xwiki.cache.config.CacheConfiguration;
40    import org.xwiki.component.annotation.Component;
41    import org.xwiki.component.descriptor.ComponentDescriptor;
42    import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
43    import org.xwiki.component.descriptor.DefaultComponentDescriptor;
44    import org.xwiki.component.internal.multi.ComponentManagerManager;
45    import org.xwiki.component.manager.ComponentLifecycleException;
46    import org.xwiki.component.manager.ComponentLookupException;
47    import org.xwiki.component.manager.ComponentManager;
48    import org.xwiki.component.manager.ComponentRepositoryException;
49    import org.xwiki.component.phase.Disposable;
50    import org.xwiki.component.phase.Initializable;
51    import org.xwiki.component.phase.InitializationException;
52    import org.xwiki.localization.TranslationBundle;
53    import org.xwiki.localization.TranslationBundleDoesNotExistsException;
54    import org.xwiki.localization.TranslationBundleFactory;
55    import org.xwiki.localization.message.TranslationMessageParser;
56    import org.xwiki.localization.wiki.internal.TranslationDocumentModel.Scope;
57    import org.xwiki.model.reference.DocumentReference;
58    import org.xwiki.model.reference.DocumentReferenceResolver;
59    import org.xwiki.model.reference.EntityReferenceSerializer;
60    import org.xwiki.model.reference.WikiReference;
61    import org.xwiki.observation.EventListener;
62    import org.xwiki.observation.ObservationManager;
63    import org.xwiki.observation.event.Event;
64    import org.xwiki.query.Query;
65    import org.xwiki.query.QueryManager;
66    import org.xwiki.security.authorization.AccessDeniedException;
67    import org.xwiki.security.authorization.AuthorizationManager;
68    import org.xwiki.security.authorization.Right;
69    import org.xwiki.wiki.descriptor.WikiDescriptorManager;
70   
71    import com.xpn.xwiki.XWikiContext;
72    import com.xpn.xwiki.XWikiException;
73    import com.xpn.xwiki.doc.XWikiDocument;
74    import com.xpn.xwiki.objects.BaseObject;
75    import com.xpn.xwiki.objects.StringProperty;
76   
77    /**
78    * Generate and manage wiki document based translations bundles.
79    *
80    * @version $Id: 5e956836ceb514ce24c63bc636d2c78056e3cdb6 $
81    * @since 4.3M2
82    */
83    @Component
84    @Named(DocumentTranslationBundleFactory.ID)
85    @Singleton
 
86    public class DocumentTranslationBundleFactory implements TranslationBundleFactory, Initializable, Disposable
87    {
88    /**
89    * The identifier of this {@link TranslationBundleFactory}.
90    */
91    public final static String ID = "document";
92   
93    /**
94    * The prefix to use in all wiki document based translations.
95    */
96    public static final String ID_PREFIX = ID + ':';
97   
98    private static final List<Event> EVENTS = Arrays.<Event>asList(new DocumentUpdatedEvent(),
99    new DocumentDeletedEvent(), new DocumentCreatedEvent());
100   
101    private static final List<Event> WIKIEVENTS = Arrays.<Event>asList(new WikiReadyEvent());
102   
103    @Inject
104    @Named("context")
105    private Provider<ComponentManager> componentManagerProvider;
106   
107    @Inject
108    @Named("uid")
109    private EntityReferenceSerializer<String> uidSerializer;
110   
111    @Inject
112    private EntityReferenceSerializer<String> serializer;
113   
114    @Inject
115    @Named("current")
116    private DocumentReferenceResolver<String> currentResolver;
117   
118    @Inject
119    private CacheManager cacheManager;
120   
121    @Inject
122    private ObservationManager observation;
123   
124    @Inject
125    private Provider<XWikiContext> xcontextProvider;
126   
127    @Inject
128    @Named("messagetool/1.0")
129    private TranslationMessageParser translationParser;
130   
131    @Inject
132    private ComponentManagerManager cmManager;
133   
134    @Inject
135    private WikiDescriptorManager wikiManager;
136   
137    @Inject
138    private Logger logger;
139   
140    @Inject
141    private QueryManager queryManager;
142   
143    @Inject
144    private AuthorizationManager authorizationManager;
145   
146    /**
147    * Used to cache on demand document bundles (those that are not registered as components).
148    */
149    private Cache<TranslationBundle> onDemandBundleCache;
150   
151    private final EventListener listener = new EventListener()
152    {
 
153  1032 toggle @Override
154    public void onEvent(Event event, Object arg1, Object arg2)
155    {
156  1032 translationDocumentUpdated((XWikiDocument) arg1);
157    }
158   
 
159  315 toggle @Override
160    public String getName()
161    {
162  315 return "localization.bundle.document";
163    }
164   
 
165  35 toggle @Override
166    public List<Event> getEvents()
167    {
168  35 return EVENTS;
169    }
170    };
171   
172    private final EventListener wikilistener = new EventListener()
173    {
 
174  4 toggle @Override
175    public void onEvent(Event event, Object arg1, Object arg2)
176    {
177  4 loadTranslations(((WikiReadyEvent) event).getWikiId());
178    }
179   
 
180  172 toggle @Override
181    public String getName()
182    {
183  172 return "localization.wikiready";
184    }
185   
 
186  35 toggle @Override
187    public List<Event> getEvents()
188    {
189  35 return WIKIEVENTS;
190    }
191    };
192   
 
193  35 toggle @Override
194    public void initialize() throws InitializationException
195    {
196    // Cache
197  35 CacheConfiguration cacheConfiguration = new CacheConfiguration("localization.bundle.document");
198   
199  35 try {
200  35 this.onDemandBundleCache = this.cacheManager.createNewCache(cacheConfiguration);
201    } catch (CacheException e) {
202  0 this.logger.error("Failed to create cache [{}]", cacheConfiguration.getConfigurationId(), e);
203    }
204   
205    // Load existing translations from main wiki, wait for WikiReaderEvent for other wikis
206   
207  35 loadTranslations(this.wikiManager.getMainWikiId());
208   
209    // Listeners
210  35 this.observation.addListener(this.listener);
211  35 this.observation.addListener(this.wikilistener);
212    }
213   
 
214  39 toggle private void loadTranslations(String wiki)
215    {
216  39 XWikiContext xcontext = this.xcontextProvider.get();
217  39 WikiReference wikiReference = new WikiReference(wiki);
218   
219  39 try {
220  39 Query query =
221    this.queryManager.createQuery(String.format(
222    "select distinct doc.fullName from Document doc, doc.object(%s) as translation",
223    TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE_STRING), Query.XWQL);
224   
225  39 query.setWiki(wiki);
226   
227  39 List<String> documents = query.execute();
228  39 for (String documentName : documents) {
229  119 DocumentReference reference = currentResolver.resolve(documentName, wikiReference);
230  119 XWikiDocument document = xcontext.getWiki().getDocument(reference, xcontext);
231   
232  119 try {
233  119 registerTranslationBundle(document);
234    } catch (Exception e) {
235  0 this.logger.error("Failed to register translation bundle from document [{}]",
236    document.getDocumentReference(), e);
237    }
238    }
239    } catch (Exception e) {
240  0 this.logger.error("Failed to load existing translations", e);
241    }
242    }
243   
 
244  1 toggle @Override
245    public TranslationBundle getBundle(String bundleId) throws TranslationBundleDoesNotExistsException
246    {
247  1 String roleHint = ID_PREFIX + bundleId;
248   
249  1 if (this.componentManagerProvider.get().hasComponent(TranslationBundle.class, roleHint)) {
250  0 try {
251  0 return this.componentManagerProvider.get().getInstance(TranslationBundle.class, roleHint);
252    } catch (ComponentLookupException e) {
253  0 this.logger.debug("Failed to lookup component [{}] with hint [{}].", TranslationBundle.class, bundleId,
254    e);
255    }
256    }
257   
258  1 return getOnDemandDocumentBundle(this.currentResolver.resolve(bundleId));
259    }
260   
261    /**
262    * Get non-component bundle.
263    */
 
264  1 toggle private TranslationBundle getOnDemandDocumentBundle(DocumentReference documentReference)
265    throws TranslationBundleDoesNotExistsException
266    {
267  1 String uid = this.uidSerializer.serialize(documentReference);
268   
269  1 TranslationBundle bundle = this.onDemandBundleCache.get(uid);
270  1 if (bundle == null) {
271  1 synchronized (this.onDemandBundleCache) {
272  1 bundle = this.onDemandBundleCache.get(uid);
273  1 if (bundle == null) {
274  1 bundle = createOnDemandDocumentBundle(documentReference, uid);
275  1 this.onDemandBundleCache.set(uid, bundle);
276    }
277    }
278    }
279   
280  1 return bundle;
281    }
282   
 
283  1 toggle private OnDemandDocumentTranslationBundle createOnDemandDocumentBundle(DocumentReference documentReference,
284    String uid) throws TranslationBundleDoesNotExistsException
285    {
286  1 XWikiContext context = this.xcontextProvider.get();
287   
288  1 XWikiDocument document;
289  1 try {
290  1 document = context.getWiki().getDocument(documentReference, context);
291    } catch (XWikiException e) {
292  0 throw new TranslationBundleDoesNotExistsException("Failed to get translation document", e);
293    }
294   
295  1 if (document.isNew()) {
296  0 throw new TranslationBundleDoesNotExistsException(String.format("Document [%s] does not exists",
297    documentReference));
298    }
299   
300  1 OnDemandDocumentTranslationBundle documentBundle;
301  1 try {
302  1 documentBundle =
303    new OnDemandDocumentTranslationBundle(ID_PREFIX, document.getDocumentReference(),
304    this.componentManagerProvider.get(), this.translationParser, this, uid);
305    } catch (ComponentLookupException e) {
306  0 throw new TranslationBundleDoesNotExistsException("Failed to create document bundle", e);
307    }
308   
309  1 return documentBundle;
310    }
311   
 
312  127 toggle private ComponentDocumentTranslationBundle createComponentDocumentBundle(XWikiDocument document,
313    ComponentDescriptor<TranslationBundle> descriptor) throws TranslationBundleDoesNotExistsException
314    {
315  127 ComponentDocumentTranslationBundle documentBundle;
316  127 try {
317  127 documentBundle =
318    new ComponentDocumentTranslationBundle(ID_PREFIX, document.getDocumentReference(),
319    this.componentManagerProvider.get(), this.translationParser, descriptor);
320    } catch (ComponentLookupException e) {
321  0 throw new TranslationBundleDoesNotExistsException("Failed to create document bundle", e);
322    }
323   
324  127 return documentBundle;
325    }
326   
327    /**
328    * @param uid remove the bundle from the cache
329    */
 
330  0 toggle void clear(String uid)
331    {
332  0 this.onDemandBundleCache.remove(uid);
333    }
334   
335    /**
336    * @param document the translation document
337    */
 
338  1032 toggle private void translationDocumentUpdated(XWikiDocument document)
339    {
340  1032 if (!document.getOriginalDocument().isNew()) {
341  520 unregisterTranslationBundle(document.getOriginalDocument());
342    }
343   
344  1032 if (!document.isNew()) {
345  878 try {
346  878 registerTranslationBundle(document);
347    } catch (Exception e) {
348  0 this.logger.error("Failed to register translation bundle from document [{}]",
349    document.getDocumentReference(), e);
350    }
351    }
352    }
353   
 
354  1517 toggle private Scope getScope(XWikiDocument document)
355    {
356  1517 BaseObject obj = document.getXObject(TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE);
357   
358  1517 if (obj != null) {
359  128 return getScope(obj);
360    }
361   
362  1389 return null;
363    }
364   
365    /**
366    * @param obj the translation object
367    * @return the {@link Scope} stored in the object, null not assigned or unknown
368    */
 
369  128 toggle private Scope getScope(BaseObject obj)
370    {
371  128 if (obj != null) {
372  128 StringProperty scopeProperty =
373    (StringProperty) obj.getField(TranslationDocumentModel.TRANSLATIONCLASS_PROP_SCOPE);
374   
375  128 if (scopeProperty != null) {
376  128 String scopeString = scopeProperty.getValue();
377   
378  128 return EnumUtils.getEnum(Scope.class, scopeString.toUpperCase());
379    }
380    }
381   
382  0 return null;
383    }
384   
385    /**
386    * @param document the translation document
387    */
 
388  520 toggle private void unregisterTranslationBundle(XWikiDocument document)
389    {
390  520 Scope scope = getScope(document);
391   
392    // Unregister component
393  520 if (scope != null && scope != Scope.ON_DEMAND) {
394  0 ComponentDescriptor<TranslationBundle> descriptor =
395    createComponentDescriptor(document.getDocumentReference());
396   
397  0 getComponentManager(document, scope, true).unregisterComponent(descriptor);
398    }
399   
400    // Remove from cache
401  520 this.onDemandBundleCache.remove(this.uidSerializer.serialize(document.getDocumentReference()));
402    }
403   
404    /**
405    * @param document the translation document
406    * @throws TranslationBundleDoesNotExistsException when no translation bundle could be created from the provided
407    * document
408    * @throws ComponentRepositoryException when the actual registration of the document bundle failed
409    * @throws AccessDeniedException when the document author does not have enough right to register the translation
410    * bundle
411    */
 
412  997 toggle private void registerTranslationBundle(XWikiDocument document) throws TranslationBundleDoesNotExistsException,
413    ComponentRepositoryException, AccessDeniedException
414    {
415  997 Scope scope = getScope(document);
416   
417  997 if (scope != null && scope != Scope.ON_DEMAND) {
418  127 checkRegistrationAuthorization(document, scope);
419   
420  127 ComponentDescriptor<TranslationBundle> descriptor =
421    createComponentDescriptor(document.getDocumentReference());
422   
423  127 ComponentDocumentTranslationBundle bundle = createComponentDocumentBundle(document, descriptor);
424   
425  127 getComponentManager(document, scope, true).registerComponent(descriptor, bundle);
426    }
427    }
428   
429    /**
430    * @param document the translation document
431    * @param scope the scope
432    * @throws AccessDeniedException thrown when the document author does not have enough right for the provided
433    * {@link Scope}
434    */
 
435  127 toggle private void checkRegistrationAuthorization(XWikiDocument document, Scope scope) throws AccessDeniedException
436    {
437  127 switch (scope) {
438  3 case GLOBAL:
439  3 this.authorizationManager.checkAccess(Right.PROGRAM, document.getAuthorReference(), null);
440  3 break;
441  124 case WIKI:
442  124 this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(), document
443    .getDocumentReference().getWikiReference());
444  124 break;
445  0 default:
446  0 break;
447    }
448    }
449   
450    /**
451    * @param documentReference the translation document reference
452    * @return the component descriptor to use to register/unregister the translation bundle
453    */
 
454  127 toggle private ComponentDescriptor<TranslationBundle> createComponentDescriptor(DocumentReference documentReference)
455    {
456  127 DefaultComponentDescriptor<TranslationBundle> descriptor = new DefaultComponentDescriptor<TranslationBundle>();
457   
458  127 descriptor.setImplementation(ComponentDocumentTranslationBundle.class);
459  127 descriptor.setInstantiationStrategy(ComponentInstantiationStrategy.SINGLETON);
460  127 descriptor.setRoleHint(ID_PREFIX + this.serializer.serialize(documentReference));
461  127 descriptor.setRoleType(TranslationBundle.class);
462   
463  127 return descriptor;
464    }
465   
466    /**
467    * Get the right component manager based on the scope.
468    *
469    * @param document the translation document
470    * @param scope the translation scope
471    * @param create true if the component manager should be created if it does not exists
472    * @return the component manager corresponding to the provided {@link Scope}
473    */
 
474  127 toggle private ComponentManager getComponentManager(XWikiDocument document, Scope scope, boolean create)
475    {
476  127 String hint;
477   
478  127 switch (scope) {
479  124 case WIKI:
480  124 hint = "wiki:" + document.getDocumentReference().getWikiReference().getName();
481  124 break;
482  0 case USER:
483  0 hint = "user:" + this.serializer.serialize(document.getAuthorReference());
484  0 break;
485  3 default:
486  3 hint = null;
487  3 break;
488    }
489   
490  127 return this.cmManager.getComponentManager(hint, create);
491    }
492   
 
493  35 toggle @Override
494    public void dispose() throws ComponentLifecycleException
495    {
496  35 this.observation.removeListener(this.listener.getName());
497  35 this.observation.removeListener(this.wikilistener.getName());
498    }
499    }