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.text.SimpleDateFormat;
029import java.util.ArrayList;
030import java.util.Calendar;
031import java.util.Date;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035
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.event.CoreEventConstants;
040import org.nuxeo.ecm.core.api.security.ACE;
041import org.nuxeo.ecm.core.api.security.ACP;
042import org.nuxeo.ecm.core.event.EventContext;
043import org.nuxeo.ecm.core.event.EventService;
044import org.nuxeo.ecm.core.event.impl.EventContextImpl;
045import org.nuxeo.ecm.core.query.sql.NXQL;
046import org.nuxeo.ecm.core.work.AbstractWork;
047import org.nuxeo.runtime.api.Framework;
048import org.nuxeo.runtime.transaction.TransactionRuntimeException;
049
050/**
051 * Work updating ACE status.
052 *
053 * @since 7.4
054 */
055public class UpdateACEStatusWork extends AbstractWork {
056
057    public static final int DEFAULT_BATCH_SIZE = 20;
058
059    public static final String ID = "updateACEStatus";
060
061    public static final String CATEGORY = "updateACEStatus";
062
063    public static final String QUERY = "SELECT ecm:uuid, ecm:acl/*1/principal, ecm:acl/*1/permission,"
064            + " ecm:acl/*1/grant, ecm:acl/*1/creator, ecm:acl/*1/begin, ecm:acl/*1/end, ecm:acl/*1/name FROM Document"
065            + " WHERE (ecm:acl/*1/status = 0 AND ecm:acl/*1/begin <= TIMESTAMP '%s')"
066            + " OR (ecm:acl/*1/status = 1 AND ecm:acl/*1/end <= TIMESTAMP '%s')";
067
068    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
069
070    protected int batchSize = DEFAULT_BATCH_SIZE;
071
072    public UpdateACEStatusWork() {
073        super(ID);
074    }
075
076    @Override
077    public void work() {
078        setStatus("Updating ACE status");
079        openSystemSession();
080
081        Date now = new Date();
082        String formattedDate = FORMATTER.format(now);
083
084        IterableQueryResult result = session.queryAndFetch(String.format(QUERY, formattedDate, formattedDate),
085                NXQL.NXQL);
086        Map<String, List<ACE>> docIdsToACEs = new HashMap<>();
087        try {
088            for (Map<String, Serializable> map : result) {
089                String docId = (String) map.get("ecm:uuid");
090                List<ACE> aces = docIdsToACEs.get(docId);
091                if (aces == null) {
092                    aces = new ArrayList<>();
093                    docIdsToACEs.put(docId, aces);
094                }
095
096                String username = (String) map.get("ecm:acl/*1/principal");
097                String permission = (String) map.get("ecm:acl/*1/permission");
098                Boolean grant = (Boolean) map.get("ecm:acl/*1/grant");
099                String creator = (String) map.get("ecm:acl/*1/creator");
100                Calendar begin = (Calendar) map.get("ecm:acl/*1/begin");
101                Calendar end = (Calendar) map.get("ecm:acl/*1/end");
102                String aclName = (String) map.get("ecm:acl/*1/name");
103                Map<String, Serializable> contextData = new HashMap<>();
104                contextData.put(CHANGED_ACL_NAME, aclName);
105                ACE ace = ACE.builder(username, permission)
106                        .isGranted(grant)
107                        .creator(creator)
108                        .begin(begin)
109                        .end(end)
110                        .contextData(contextData)
111                        .build();
112                aces.add(ace);
113            }
114        } finally {
115            result.close();
116        }
117
118        int acpUpdatedCount = 0;
119        Map<DocumentRef, List<ACE>> processedRefToACEs = new HashMap<>();
120        for (Map.Entry<String, List<ACE>> entry : docIdsToACEs.entrySet()) {
121            try {
122                DocumentRef ref = new IdRef(entry.getKey());
123                ACP acp = session.getACP(ref);
124                // re-set the ACP to actually write the new status
125                session.setACP(ref, acp, true);
126                acpUpdatedCount++;
127                processedRefToACEs.put(ref, entry.getValue());
128                if (acpUpdatedCount % batchSize == 0) {
129                    fireACEStatusUpdatedEvent(processedRefToACEs);
130                    commitOrRollbackTransaction();
131                    startTransaction();
132                    processedRefToACEs.clear();
133                }
134            } catch (TransactionRuntimeException e) {
135                if (e.getMessage().contains("Transaction timeout")) {
136                    batchSize = 1;
137                }
138                throw e;
139            }
140        }
141        fireACEStatusUpdatedEvent(processedRefToACEs);
142
143        setStatus(null);
144    }
145
146    protected void fireACEStatusUpdatedEvent(Map<DocumentRef, List<ACE>> refToACEs) {
147        EventContext eventContext = new EventContextImpl(session, session.getPrincipal());
148        eventContext.setProperty(DOCUMENT_REFS, (Serializable) refToACEs);
149        eventContext.setProperty(REPOSITORY_NAME, session.getRepositoryName());
150        Framework.getService(EventService.class).fireEvent(ACE_STATUS_UPDATED, eventContext);
151    }
152
153    @Override
154    public String getCategory() {
155        return CATEGORY;
156    }
157
158    @Override
159    public String getTitle() {
160        return "Updating ACE status";
161    }
162
163    @Override
164    public int getRetryCount() {
165        return 10;
166    }
167}