001/*
002 * (C) Copyright 2018-2019 Nuxeo (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 *     Funsho David
018 */
019
020package org.nuxeo.ecm.core.bulk.action;
021
022import static org.nuxeo.ecm.core.api.event.CoreEventConstants.REPOSITORY_NAME;
023import static org.nuxeo.ecm.core.api.event.DocumentEventCategories.EVENT_DOCUMENT_CATEGORY;
024import static org.nuxeo.ecm.core.api.trash.TrashService.DOCUMENT_TRASHED;
025import static org.nuxeo.ecm.core.api.trash.TrashService.DOCUMENT_UNTRASHED;
026import static org.nuxeo.ecm.core.bulk.BulkServiceImpl.STATUS_STREAM;
027import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_NAME;
028import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_PARENTID;
029import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_UUID;
030import static org.nuxeo.ecm.core.query.sql.NXQL.NXQL;
031import static org.nuxeo.lib.stream.computation.AbstractComputation.INPUT_1;
032import static org.nuxeo.lib.stream.computation.AbstractComputation.OUTPUT_1;
033
034import java.io.Serializable;
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.Collection;
038import java.util.HashSet;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042
043import org.apache.logging.log4j.LogManager;
044import org.apache.logging.log4j.Logger;
045import org.nuxeo.ecm.core.api.CoreSession;
046import org.nuxeo.ecm.core.api.DocumentModelList;
047import org.nuxeo.ecm.core.api.DocumentRef;
048import org.nuxeo.ecm.core.api.IdRef;
049import org.nuxeo.ecm.core.api.IterableQueryResult;
050import org.nuxeo.ecm.core.api.NuxeoException;
051import org.nuxeo.ecm.core.api.PropertyException;
052import org.nuxeo.ecm.core.api.trash.TrashService;
053import org.nuxeo.ecm.core.bulk.action.computation.AbstractBulkComputation;
054import org.nuxeo.ecm.core.event.Event;
055import org.nuxeo.ecm.core.event.EventService;
056import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
057import org.nuxeo.lib.stream.computation.Topology;
058import org.nuxeo.runtime.api.Framework;
059import org.nuxeo.runtime.stream.StreamProcessorTopology;
060
061/**
062 * @since 10.3
063 */
064public class TrashAction implements StreamProcessorTopology {
065
066    public static final String ACTION_NAME = "trash";
067
068    public static final String ACTION_FULL_NAME = "bulk/" + ACTION_NAME;
069
070    public static final String PARAM_NAME = "value";
071
072    public static final String PROXY_QUERY_TEMPLATE = "SELECT ecm:uuid FROM Document WHERE ecm:isProxy=1 AND ecm:uuid IN ('%s')";
073
074    public static final String SYSPROP_QUERY_TEMPLATE = "SELECT ecm:uuid, ecm:name, ecm:parentId FROM Document WHERE ecm:isProxy=0 AND ecm:isTrashed=%s AND ecm:uuid IN ('%s')";
075
076    @Override
077    public Topology getTopology(Map<String, String> options) {
078        return Topology.builder()
079                       .addComputation(TrashComputation::new,
080                               Arrays.asList(INPUT_1 + ":" + ACTION_FULL_NAME, OUTPUT_1 + ":" + STATUS_STREAM))
081                       .build();
082    }
083
084    public static class TrashComputation extends AbstractBulkComputation {
085
086        private static final Logger log = LogManager.getLogger(TrashComputation.class);
087
088        public TrashComputation() {
089            super(ACTION_FULL_NAME);
090        }
091
092        @Override
093        protected void compute(CoreSession session, List<String> ids, Map<String, Serializable> properties) {
094            Boolean trashValue = (Boolean) properties.get(PARAM_NAME);
095            if (trashValue) {
096                removeProxies(session, ids);
097            }
098            setSystemProperty(session, ids, trashValue);
099        }
100
101        protected void removeProxies(CoreSession session, List<String> ids) {
102            Set<DocumentRef> proxies = new HashSet<>();
103            String query = String.format(PROXY_QUERY_TEMPLATE, String.join("', '", ids));
104            try (IterableQueryResult res = session.queryAndFetch(query, NXQL)) {
105                for (Map<String, Serializable> map : res) {
106                    proxies.add(new IdRef((String) map.get(ECM_UUID)));
107                }
108            }
109            session.removeDocuments(proxies.toArray(new DocumentRef[0]));
110            try {
111                session.save();
112            } catch (PropertyException e) {
113                // TODO send to error stream
114                log.warn("Cannot save session", e);
115            }
116        }
117
118        public void setSystemProperty(CoreSession session, List<String> ids, Boolean value) {
119            List<DocumentRef> updatedRefs = new ArrayList<>(ids.size());
120            String query = String.format(SYSPROP_QUERY_TEMPLATE, value ? "0" : "1", String.join("', '", ids));
121            try (IterableQueryResult res = session.queryAndFetch(query, NXQL)) {
122                TrashService trashService = Framework.getService(TrashService.class);
123                for (Map<String, Serializable> map : res) {
124                    DocumentRef ref = new IdRef((String) map.get(ECM_UUID));
125                    try {
126                        session.setDocumentSystemProp(ref, "isTrashed", value);
127                        String docName = (String) map.get(ECM_NAME);
128                        if (!value && trashService.isMangledName(docName)) {
129                            DocumentRef parentRef = new IdRef((String) map.get(ECM_PARENTID));
130                            session.move(ref, parentRef,
131                                    trashService.unmangleName(session, parentRef, docName));
132                        }
133                        updatedRefs.add(ref);
134                    } catch (NuxeoException e) {
135                        // TODO send to error stream
136                        log.warn("Cannot set system property: isTrashed on: " + ref.toString(), e);
137                    }
138                }
139            }
140            try {
141                session.save();
142                if (!updatedRefs.isEmpty()) {
143                    fireEvent(session, value ? DOCUMENT_TRASHED : DOCUMENT_UNTRASHED, updatedRefs);
144                }
145            } catch (PropertyException e) {
146                // TODO send to error stream
147                log.warn("Cannot save session", e);
148            }
149        }
150
151        protected void fireEvent(CoreSession session, String eventId, Collection<DocumentRef> refs) {
152            EventService eventService = Framework.getService(EventService.class);
153            DocumentModelList docs = session.getDocuments(refs.toArray(new DocumentRef[0]));
154            docs.forEach(d -> {
155                DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), d);
156                ctx.setProperty(REPOSITORY_NAME, session.getRepositoryName());
157                ctx.setCategory(EVENT_DOCUMENT_CATEGORY);
158                Event event = ctx.newEvent(eventId);
159                event.setImmediate(false);
160                event.setInline(false);
161                eventService.fireEvent(event);
162            });
163        }
164    }
165}