org.xwiki.localization.wiki.internal.DocumentTranslationBundleFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.localization.wiki.internal.DocumentTranslationBundleFactory.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.localization.wiki.internal;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.apache.commons.lang3.EnumUtils;
import org.slf4j.Logger;
import org.xwiki.cache.Cache;
import org.xwiki.cache.CacheException;
import org.xwiki.cache.CacheManager;
import org.xwiki.cache.config.CacheConfiguration;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.descriptor.ComponentDescriptor;
import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
import org.xwiki.component.descriptor.DefaultComponentDescriptor;
import org.xwiki.component.internal.multi.ComponentManagerManager;
import org.xwiki.component.manager.ComponentLifecycleException;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.component.manager.ComponentRepositoryException;
import org.xwiki.component.phase.Disposable;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.localization.TranslationBundle;
import org.xwiki.localization.TranslationBundleDoesNotExistsException;
import org.xwiki.localization.TranslationBundleFactory;
import org.xwiki.localization.message.TranslationMessageParser;
import org.xwiki.localization.wiki.internal.TranslationDocumentModel.Scope;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.RegexEntityReference;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.observation.EventListener;
import org.xwiki.observation.ObservationManager;
import org.xwiki.observation.event.Event;
import org.xwiki.query.Query;
import org.xwiki.query.QueryManager;
import org.xwiki.security.authorization.AccessDeniedException;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.event.XObjectAddedEvent;
import com.xpn.xwiki.internal.event.XObjectDeletedEvent;
import com.xpn.xwiki.internal.event.XObjectUpdatedEvent;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.StringProperty;

/**
 * Generate and manager wiki document based translations bundles.
 * 
 * @version $Id: 120069d89136ac6f7effd5af696f66a55329ea53 $
 * @since 4.3M2
 */
@Component
@Named("document")
@Singleton
public class DocumentTranslationBundleFactory implements TranslationBundleFactory, Initializable, Disposable {
    private static final RegexEntityReference TRANSLATIONOBJET = new RegexEntityReference(
            Pattern.compile("[^:]+:" + TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE_STRING + "\\[\\d*\\]"),
            EntityType.OBJECT);

    private static final List<Event> EVENTS = Arrays.<Event>asList(new XObjectAddedEvent(TRANSLATIONOBJET),
            new XObjectUpdatedEvent(TRANSLATIONOBJET), new XObjectDeletedEvent(TRANSLATIONOBJET));

    @Inject
    @Named("context")
    private Provider<ComponentManager> componentManagerProvider;

    @Inject
    @Named("uid")
    private EntityReferenceSerializer<String> uidSerializer;

    @Inject
    private EntityReferenceSerializer<String> serializer;

    @Inject
    @Named("current")
    private DocumentReferenceResolver<String> currentResolver;

    @Inject
    private CacheManager cacheManager;

    @Inject
    private ObservationManager observation;

    @Inject
    private Provider<XWikiContext> xcontextProvider;

    @Inject
    @Named("messagetool/1.0")
    private TranslationMessageParser translationParser;

    @Inject
    private ComponentManagerManager cmManager;

    @Inject
    private Logger logger;

    @Inject
    private QueryManager queryManager;

    @Inject
    private AuthorizationManager authorizationManager;

    private Cache<TranslationBundle> bundlesCache;

    private EventListener listener = new EventListener() {
        @Override
        public void onEvent(Event event, Object arg1, Object arg2) {
            XWikiDocument document = (XWikiDocument) arg1;

            if (event instanceof XObjectAddedEvent) {
                translationObjectAdded(document);
            } else if (event instanceof XObjectDeletedEvent) {
                translationObjectDeleted(document);
            } else {
                translationObjectUpdated(document);
            }
        }

        @Override
        public String getName() {
            return "localization.bundle.document";
        }

        @Override
        public List<Event> getEvents() {
            return EVENTS;
        }
    };

    @Override
    public void initialize() throws InitializationException {
        // Cache
        CacheConfiguration cacheConfiguration = new CacheConfiguration("localization.bundle.document");

        try {
            this.bundlesCache = this.cacheManager.createNewCache(cacheConfiguration);
        } catch (CacheException e) {
            this.logger.error("Failed to create cache [{}]", cacheConfiguration.getConfigurationId(), e);
        }

        // Load existing translations

        XWikiContext xcontext = this.xcontextProvider.get();

        Set<String> wikis;
        try {
            wikis = new HashSet<String>(xcontext.getWiki().getVirtualWikisDatabaseNames(xcontext));
        } catch (XWikiException e) {
            this.logger.error("Failed to list existing wikis", e);
            wikis = new HashSet<String>();
        }

        if (!wikis.contains(xcontext.getMainXWiki())) {
            wikis.add(xcontext.getMainXWiki());
        }

        for (String wiki : wikis) {
            loadTranslations(wiki, xcontext);
        }

        // Listener
        this.observation.addListener(this.listener);
    }

    private void loadTranslations(String wiki, XWikiContext xcontext) {
        try {
            Query query = this.queryManager.createQuery(
                    String.format("select doc.space, doc.name from Document doc, doc.object(%s) as translation",
                            TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE_STRING),
                    Query.XWQL);

            query.setWiki(wiki);

            List<Object[]> documents = query.execute();
            for (Object[] documentName : documents) {
                DocumentReference reference = new DocumentReference(wiki, (String) documentName[0],
                        (String) documentName[1]);

                XWikiDocument document = xcontext.getWiki().getDocument(reference, xcontext);

                registerTranslationBundle(document);
            }
        } catch (Exception e) {
            this.logger.error("Failed to load eexisting translations", e);
        }
    }

    @Override
    public TranslationBundle getBundle(String bundleId) throws TranslationBundleDoesNotExistsException {
        String id = AbstractDocumentTranslationBundle.ID_PREFIX + bundleId;

        if (this.componentManagerProvider.get().hasComponent(TranslationBundle.class, id)) {
            try {
                return this.componentManagerProvider.get().getInstance(TranslationBundle.class, id);
            } catch (ComponentLookupException e) {
                this.logger.debug("Failed to lookup component [{}] with hint [{}].", TranslationBundle.class,
                        bundleId, e);
            }
        }

        return getDocumentBundle(this.currentResolver.resolve(bundleId));
    }

    private TranslationBundle getDocumentBundle(DocumentReference documentReference)
            throws TranslationBundleDoesNotExistsException {
        String uid = this.uidSerializer.serialize(documentReference);

        TranslationBundle bundle = this.bundlesCache.get(uid);
        if (bundle == null) {
            synchronized (this.bundlesCache) {
                bundle = this.bundlesCache.get(uid);
                if (bundle == null) {
                    bundle = createDocumentBundle(documentReference);
                }
            }
        }

        return bundle;
    }

    private DefaultDocumentTranslationBundle createDocumentBundle(DocumentReference documentReference)
            throws TranslationBundleDoesNotExistsException {
        XWikiContext context = this.xcontextProvider.get();

        XWikiDocument document;
        try {
            document = context.getWiki().getDocument(documentReference, context);
        } catch (XWikiException e) {
            throw new TranslationBundleDoesNotExistsException("Failed to get translation document", e);
        }

        if (document.isNew()) {
            throw new TranslationBundleDoesNotExistsException(
                    String.format("Document [%s] does not exists", documentReference));
        }

        return createDocumentBundle(document);
    }

    private DefaultDocumentTranslationBundle createDocumentBundle(XWikiDocument document)
            throws TranslationBundleDoesNotExistsException {
        BaseObject translationObject = document.getXObject(TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE);

        if (translationObject == null) {
            throw new TranslationBundleDoesNotExistsException(
                    String.format("[%s] is not a translation document", document));
        }

        DefaultDocumentTranslationBundle documentBundle;
        try {
            documentBundle = new DefaultDocumentTranslationBundle(document.getDocumentReference(),
                    this.componentManagerProvider.get(), this.translationParser);
        } catch (ComponentLookupException e) {
            throw new TranslationBundleDoesNotExistsException("Failed to create document bundle", e);
        }

        return documentBundle;
    }

    /**
     * @param document the translation document
     */
    private void translationObjectUpdated(XWikiDocument document) {
        unregisterTranslationBundle(document.getOriginalDocument());
        try {
            registerTranslationBundle(document);
        } catch (Exception e) {
            this.logger.error("Failed to register translation bundle from document [{}]",
                    document.getDocumentReference(), e);
        }
    }

    /**
     * @param document the translation document
     */
    private void translationObjectDeleted(XWikiDocument document) {
        unregisterTranslationBundle(document.getOriginalDocument());
    }

    /**
     * @param document the translation document
     */
    private void translationObjectAdded(XWikiDocument document) {
        try {
            registerTranslationBundle(document);
        } catch (Exception e) {
            this.logger.error("Failed to register translation bundle from document [{}]",
                    document.getDocumentReference(), e);
        }
    }

    /**
     * @param obj the translation object
     * @return the {@link Scope} stored in the object, null not assigned or unknown
     */
    private Scope getScope(BaseObject obj) {
        if (obj != null) {
            StringProperty scopeProperty = (StringProperty) obj
                    .getField(TranslationDocumentModel.TRANSLATIONCLASS_PROP_SCOPE);

            if (scopeProperty != null) {
                String scopeString = scopeProperty.getValue();

                return EnumUtils.getEnum(Scope.class, scopeString.toUpperCase());
            }
        }

        return null;
    }

    /**
     * @param document the translation document
     */
    private void unregisterTranslationBundle(XWikiDocument document) {
        Scope scope = getScope(document.getXObject(TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE));

        // Unregister component
        if (scope != null && scope != Scope.ON_DEMAND) {
            ComponentDescriptor<TranslationBundle> descriptor = createComponentDescriptor(
                    document.getDocumentReference());

            getComponentManager(document, scope, true).unregisterComponent(descriptor);
        }

        // Remove from cache
        this.bundlesCache.remove(this.uidSerializer.serialize(document.getDocumentReference()));
    }

    /**
     * @param document the translation document
     * @throws TranslationBundleDoesNotExistsException when no translation bundle could be created from the provided
     *             document
     * @throws ComponentRepositoryException when the actual registration of the document bundle failed
     * @throws AccessDeniedException when the document author does not have enough right to register the translation
     *             bundle
     */
    private void registerTranslationBundle(XWikiDocument document)
            throws TranslationBundleDoesNotExistsException, ComponentRepositoryException, AccessDeniedException {
        Scope scope = getScope(document.getXObject(TranslationDocumentModel.TRANSLATIONCLASS_REFERENCE));

        if (scope != null && scope != Scope.ON_DEMAND) {
            checkRegistrationAuthorization(document, scope);

            DefaultDocumentTranslationBundle bundle = createDocumentBundle(document);

            ComponentDescriptor<TranslationBundle> descriptor = createComponentDescriptor(
                    document.getDocumentReference());

            getComponentManager(document, scope, true).registerComponent(descriptor, bundle);
        }
    }

    /**
     * @param document the translation document
     * @param scope the scope
     * @throws AccessDeniedException thrown when the document author does not have enough right for the provided
     *             {@link Scope}
     */
    private void checkRegistrationAuthorization(XWikiDocument document, Scope scope) throws AccessDeniedException {
        switch (scope) {
        case GLOBAL:
            this.authorizationManager.checkAccess(Right.PROGRAM, document.getAuthorReference(),
                    new WikiReference(this.xcontextProvider.get().getMainXWiki()));
            break;
        case WIKI:
            this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(),
                    document.getDocumentReference().getWikiReference());
            break;
        default:
            break;
        }
    }

    /**
     * @param documentReference the translation document reference
     * @return the component descriptor to use to register/unregister the translation bundle
     */
    private ComponentDescriptor<TranslationBundle> createComponentDescriptor(DocumentReference documentReference) {
        DefaultComponentDescriptor<TranslationBundle> descriptor = new DefaultComponentDescriptor<TranslationBundle>();

        descriptor.setImplementation(DefaultDocumentTranslationBundle.class);
        descriptor.setInstantiationStrategy(ComponentInstantiationStrategy.SINGLETON);
        descriptor.setRoleHint(
                AbstractDocumentTranslationBundle.ID_PREFIX + this.serializer.serialize(documentReference));
        descriptor.setRoleType(TranslationBundle.class);

        return descriptor;
    }

    /**
     * Get the right component manager based on the scope.
     * 
     * @param document the translation document
     * @param scope the translation scope
     * @param create true if the component manager should be created if it does not exists
     * @return the component manager corresponding to the provided {@link Scope}
     */
    private ComponentManager getComponentManager(XWikiDocument document, Scope scope, boolean create) {
        String hint;

        switch (scope) {
        case WIKI:
            hint = "wiki:" + document.getDocumentReference().getWikiReference().getName();
            break;
        case USER:
            hint = "user:" + this.serializer.serialize(document.getAuthorReference());
            break;
        default:
            hint = "root";
            break;
        }

        return this.cmManager.getComponentManager(hint, create);
    }

    @Override
    public void dispose() throws ComponentLifecycleException {
        this.observation.removeListener(this.listener.getName());
    }
}