org.xwiki.extension.script.ExtensionHistoryScriptService.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.extension.script.ExtensionHistoryScriptService.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.extension.script;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

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

import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.PredicateUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.extension.job.AbstractExtensionRequest;
import org.xwiki.extension.job.history.ExtensionJobHistory;
import org.xwiki.extension.job.history.ExtensionJobHistoryRecord;
import org.xwiki.extension.job.history.ExtensionJobHistorySerializer;
import org.xwiki.extension.job.history.ReplayJobStatus;
import org.xwiki.extension.job.history.ReplayRequest;
import org.xwiki.extension.job.history.internal.ReplayJob;
import org.xwiki.job.AbstractRequest;
import org.xwiki.job.Job;
import org.xwiki.job.JobException;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.security.authorization.Right;

/**
 * Various script APIs related to extension job history.
 * 
 * @version $Id: 0cb46887f40c14874612843de1327178219be5b2 $
 * @since 7.1M2
 */
@Component
@Named(ExtensionManagerScriptService.ROLEHINT + '.' + ExtensionHistoryScriptService.ID)
@Singleton
public class ExtensionHistoryScriptService extends AbstractExtensionScriptService {
    /**
     * Utility class to build filters for the extension job history.
     */
    public class ExtensionHistoryFilter {
        /**
         * The list of constraints applied by this filter.
         */
        private final List<Predicate<ExtensionJobHistoryRecord>> constraints = new ArrayList<>();

        /**
         * Filters the history records with the specified job type.
         * 
         * @param jobTypes the type of jobs that should be included in the result
         * @return this
         */
        public ExtensionHistoryFilter ofType(final List<String> jobTypes) {
            this.constraints.add(new Predicate<ExtensionJobHistoryRecord>() {
                @Override
                public boolean evaluate(ExtensionJobHistoryRecord record) {
                    return jobTypes.contains(record.getJobType());
                }
            });
            return this;
        }

        /**
         * Filters the jobs that have been executed on the current wiki or on the entire farm.
         * 
         * @return this
         */
        public ExtensionHistoryFilter fromThisWiki() {
            final String currentWikiNamespace = WIKI_NAMESPACE_PREFIX + xcontextProvider.get().getWikiId();
            this.constraints.add(new Predicate<ExtensionJobHistoryRecord>() {
                @Override
                public boolean evaluate(ExtensionJobHistoryRecord record) {
                    return !record.getRequest().hasNamespaces()
                            || record.getRequest().getNamespaces().contains(currentWikiNamespace);
                }
            });
            return this;
        }

        /**
         * Lists the history records that match this filter and that are older than the specified offset record.
         * 
         * @param offsetRecordId specifies the offset record (where to start from); pass {@code null} to start from the
         *            most recent record in the history
         * @param limit the maximum number of records to return from the specified offset
         * @return a list of history records that match this filter and are older than the specified offset record
         */
        public List<ExtensionJobHistoryRecord> list(String offsetRecordId, int limit) {
            return history.getRecords(PredicateUtils.allPredicate(this.constraints), offsetRecordId, limit);
        }
    }

    /**
     * The identifier of the sub extension {@link org.xwiki.script.service.ScriptService}.
     */
    public static final String ID = "history";

    @Inject
    private ExtensionJobHistory history;

    @Inject
    private ExtensionJobHistorySerializer serializer;

    /**
     * @return a new history filter
     */
    public ExtensionHistoryFilter getRecords() {
        return new ExtensionHistoryFilter();
    }

    /**
     * Serializes a history record.
     * 
     * @param record the history record to serialize
     * @return the string serialization of the given history record
     */
    public String serialize(ExtensionJobHistoryRecord record) {
        setError(null);

        try {
            return this.serializer.serialize(record);
        } catch (Exception e) {
            setError(e);
            return null;
        }
    }

    /**
     * Deserializes a list of history records.
     * 
     * @param serializedHistoryRecords the serialized list of history records
     * @return the list of history records
     */
    public List<ExtensionJobHistoryRecord> deserialize(String serializedHistoryRecords) {
        setError(null);

        try {
            return this.serializer.deserialize(serializedHistoryRecords);
        } catch (Exception e) {
            setError(e);
            return null;
        }
    }

    /**
     * Reads a list of history records from a given input stream (e.g. the attachment content input stream).
     * 
     * @param inputStream an input stream that provides a list of serialized history records
     * @return the list of history records
     */
    public List<ExtensionJobHistoryRecord> read(InputStream inputStream) {
        setError(null);

        try {
            return this.serializer.read(new InputStreamReader(inputStream));
        } catch (Exception e) {
            setError(e);
            return null;
        }
    }

    /**
     * Replay the given list of extension history records.
     * 
     * @param records the history records to replay
     * @return the {@link Job} object that can be used to monitor the progress of the replay process, or {@code null} in
     *         case of failure
     */
    public Job replay(List<ExtensionJobHistoryRecord> records) {
        setError(null);

        ReplayRequest request = createReplayRequest(createReplayPlan(records, true, null));

        try {
            return this.jobExecutor.execute(ReplayJob.JOB_TYPE, request);
        } catch (JobException e) {
            setError(e);
            return null;
        }
    }

    /**
     * Prepares a list of history records for replay.
     * 
     * @param records the history records to prepare for replay
     * @param preserveUsers {@code true} if the given history records should be replayed using their original users,
     *            {@code false} if the current user should be used instead
     * @param namespaces the namespaces where to replay the given history records; pass {@code null} or an empty
     *            collection if you want to preserve the original namespaces
     * @return the modified history records, prepared to be replayed
     */
    public List<ExtensionJobHistoryRecord> createReplayPlan(List<ExtensionJobHistoryRecord> records,
            boolean preserveUsers, Collection<String> namespaces) {
        String currentWiki = this.xcontextProvider.get().getWikiId();
        if (!this.authorization.hasAccess(Right.ADMIN, new WikiReference(currentWiki))) {
            return Collections.emptyList();
        } else if (!this.authorization.hasAccess(Right.PROGRAM)) {
            // Replay on the current wiki using the current user.
            return createReplayPlanInternal(records, false, Arrays.asList(WIKI_NAMESPACE_PREFIX + currentWiki));
        } else {
            // Only the users that have PR can preserve the rights-related properties of the original extension request.
            return createReplayPlanInternal(records, preserveUsers, namespaces);
        }
    }

    /**
     * @param id identifies the replay job
     * @return the status of the specified replay job
     */
    public ReplayJobStatus getReplayJobStatus(String id) {
        return (ReplayJobStatus) getJobStatus(getReplayJobId(id));
    }

    private List<ExtensionJobHistoryRecord> createReplayPlanInternal(List<ExtensionJobHistoryRecord> records,
            boolean preserveUsers, Collection<String> namespaces) {
        for (ExtensionJobHistoryRecord record : records) {
            if (!preserveUsers) {
                setRightsProperties((AbstractRequest) record.getRequest());
            }
            if (record.getRequest().hasNamespaces() && namespaces != null && namespaces.size() > 0) {
                ((AbstractRequest) record.getRequest()).setProperty("namespaces", namespaces);
            }
        }
        return records;
    }

    private ReplayRequest createReplayRequest(List<ExtensionJobHistoryRecord> records) {
        ReplayRequest request = new ReplayRequest();
        String suffix = new Date().getTime() + "-" + ThreadLocalRandom.current().nextInt(100, 1000);
        request.setId(getReplayJobId(suffix));
        // There may be questions for which there isn't a specified answer.
        request.setInteractive(true);
        request.setRecords(records);

        // Provide information on what started the job.
        request.setProperty(PROPERTY_CONTEXT_WIKI, this.xcontextProvider.get().getWikiId());
        request.setProperty(PROPERTY_CONTEXT_ACTION, this.xcontextProvider.get().getAction());

        setRightsProperties(request);

        return request;
    }

    private List<String> getReplayJobId(String suffix) {
        return Arrays.asList(AbstractExtensionRequest.JOBID_PREFIX, ID, suffix);
    }
}