001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Thomas Roger
018 */
019
020package org.nuxeo.ecm.core.security;
021
022import static org.nuxeo.ecm.core.api.event.CoreEventConstants.CHANGED_ACL_NAME;
023import static org.nuxeo.ecm.core.api.event.CoreEventConstants.DOCUMENT_REFS;
024import static org.nuxeo.ecm.core.api.event.CoreEventConstants.REPOSITORY_NAME;
025import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.ACE_STATUS_UPDATED;
026
027import java.io.Serializable;
028import java.util.ArrayList;
029import java.util.Calendar;
030import java.util.Date;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034
035import org.apache.commons.lang3.time.FastDateFormat;
036import org.nuxeo.ecm.core.api.DocumentRef;
037import org.nuxeo.ecm.core.api.IdRef;
038import org.nuxeo.ecm.core.api.IterableQueryResult;
039import org.nuxeo.ecm.core.api.security.ACE;
040import org.nuxeo.ecm.core.api.security.ACP;
041import org.nuxeo.ecm.core.event.EventContext;
042import org.nuxeo.ecm.core.event.EventService;
043import org.nuxeo.ecm.core.event.impl.EventContextImpl;
044import org.nuxeo.ecm.core.query.sql.NXQL;
045import org.nuxeo.ecm.core.work.AbstractWork;
046import org.nuxeo.runtime.api.Framework;
047import org.nuxeo.runtime.transaction.TransactionRuntimeException;
048
049/**
050 * Work updating ACE status.
051 *
052 * @since 7.4
053 */
054public class UpdateACEStatusWork extends AbstractWork {
055
056    public static final int DEFAULT_BATCH_SIZE = 20;
057
058    public static final String CATEGORY = "updateACEStatus";
059
060    public static final String QUERY = "SELECT ecm:uuid, ecm:acl/*1/principal, ecm:acl/*1/permission,"
061            + " ecm:acl/*1/grant, ecm:acl/*1/creator, ecm:acl/*1/begin, ecm:acl/*1/end, ecm:acl/*1/name FROM Document"
062            + " WHERE (ecm:acl/*1/status = 0 AND ecm:acl/*1/begin <= TIMESTAMP '%s')"
063            + " OR (ecm:acl/*1/status = 1 AND ecm:acl/*1/end <= TIMESTAMP '%s')";
064
065    public static final FastDateFormat FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
066
067    protected int batchSize = DEFAULT_BATCH_SIZE;
068
069    @Override
070    public void work() {
071        setStatus("Updating ACE status");
072        openSystemSession();
073
074        Date now = new Date();
075        String formattedDate = FORMATTER.format(now);
076
077        IterableQueryResult result = session.queryAndFetch(String.format(QUERY, formattedDate, formattedDate),
078                NXQL.NXQL);
079        Map<String, List<ACE>> docIdsToACEs = new HashMap<>();
080        try {
081            for (Map<String, Serializable> map : result) {
082                String docId = (String) map.get("ecm:uuid");
083                List<ACE> aces = docIdsToACEs.get(docId);
084                if (aces == null) {
085                    aces = new ArrayList<>();
086                    docIdsToACEs.put(docId, aces);
087                }
088
089                String username = (String) map.get("ecm:acl/*1/principal");
090                String permission = (String) map.get("ecm:acl/*1/permission");
091                Boolean grant = (Boolean) map.get("ecm:acl/*1/grant");
092                String creator = (String) map.get("ecm:acl/*1/creator");
093                Calendar begin = (Calendar) map.get("ecm:acl/*1/begin");
094                Calendar end = (Calendar) map.get("ecm:acl/*1/end");
095                String aclName = (String) map.get("ecm:acl/*1/name");
096                Map<String, Serializable> contextData = new HashMap<>();
097                contextData.put(CHANGED_ACL_NAME, aclName);
098                ACE ace = ACE.builder(username, permission)
099                        .isGranted(grant)
100                        .creator(creator)
101                        .begin(begin)
102                        .end(end)
103                        .contextData(contextData)
104                        .build();
105                aces.add(ace);
106            }
107        } finally {
108            result.close();
109        }
110
111        int acpUpdatedCount = 0;
112        Map<DocumentRef, List<ACE>> processedRefToACEs = new HashMap<>();
113        for (Map.Entry<String, List<ACE>> entry : docIdsToACEs.entrySet()) {
114            try {
115                DocumentRef ref = new IdRef(entry.getKey());
116                ACP acp = session.getACP(ref);
117                // re-set the ACP to actually write the new status
118                session.setACP(ref, acp, true);
119                acpUpdatedCount++;
120                processedRefToACEs.put(ref, entry.getValue());
121                if (acpUpdatedCount % batchSize == 0) {
122                    fireACEStatusUpdatedEvent(processedRefToACEs);
123                    commitOrRollbackTransaction();
124                    startTransaction();
125                    processedRefToACEs.clear();
126                }
127            } catch (TransactionRuntimeException e) {
128                if (e.getMessage().contains("Transaction timeout")) {
129                    batchSize = 1;
130                }
131                throw e;
132            }
133        }
134        fireACEStatusUpdatedEvent(processedRefToACEs);
135
136        setStatus(null);
137    }
138
139    protected void fireACEStatusUpdatedEvent(Map<DocumentRef, List<ACE>> refToACEs) {
140        EventContext eventContext = new EventContextImpl(session, session.getPrincipal());
141        eventContext.setProperty(DOCUMENT_REFS, (Serializable) refToACEs);
142        eventContext.setProperty(REPOSITORY_NAME, session.getRepositoryName());
143        Framework.getService(EventService.class).fireEvent(ACE_STATUS_UPDATED, eventContext);
144    }
145
146    @Override
147    public String getCategory() {
148        return CATEGORY;
149    }
150
151    @Override
152    public String getTitle() {
153        return "Updating ACE status";
154    }
155
156    @Override
157    public int getRetryCount() {
158        return 10;
159    }
160}