001/*
002 * (C) Copyright 2018 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 *       Kevin Leturc <kleturc@nuxeo.com>
018 */
019package org.nuxeo.ecm.core.bulk.message;
020
021import static org.apache.commons.lang3.StringUtils.isEmpty;
022
023import java.io.Serializable;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.Map;
027import java.util.UUID;
028
029import org.apache.avro.reflect.AvroEncode;
030import org.apache.avro.reflect.Nullable;
031import org.apache.commons.lang3.builder.EqualsBuilder;
032import org.apache.commons.lang3.builder.HashCodeBuilder;
033import org.apache.commons.lang3.builder.ToStringBuilder;
034
035/**
036 * A message representing a bulk command
037 *
038 * @since 10.2
039 */
040public class BulkCommand implements Serializable {
041
042    private static final long serialVersionUID = 20181021L;
043
044    protected String id;
045
046    protected String action;
047
048    protected String query;
049
050    @Nullable
051    protected String username;
052
053    @Nullable
054    protected String repository;
055
056    protected int bucketSize;
057
058    protected int batchSize;
059
060    @AvroEncode(using = MapAsJsonAsStringEncoding.class)
061    protected Map<String, Serializable> params;
062
063    protected BulkCommand() {
064        // Empty constructor for Avro decoder
065    }
066
067    public BulkCommand(Builder builder) {
068        this.id = UUID.randomUUID().toString();
069        this.username = builder.username;
070        this.repository = builder.repository;
071        this.query = builder.query;
072        this.action = builder.action;
073        this.bucketSize = builder.bucketSize;
074        this.batchSize = builder.batchSize;
075        this.params = builder.params;
076    }
077
078    public String getUsername() {
079        return username;
080    }
081
082    public String getRepository() {
083        return repository;
084    }
085
086    public String getQuery() {
087        return query;
088    }
089
090    public String getAction() {
091        return action;
092    }
093
094    public Map<String, Serializable> getParams() {
095        return Collections.unmodifiableMap(params);
096    }
097
098    @SuppressWarnings("unchecked")
099    public <T> T getParam(String key) {
100        return (T) params.get(key);
101    }
102
103    public String getId() {
104        return id;
105    }
106
107    public int getBucketSize() {
108        return bucketSize;
109    }
110
111    public int getBatchSize() {
112        return batchSize;
113    }
114
115    @Override
116    public int hashCode() {
117        return HashCodeBuilder.reflectionHashCode(this);
118    }
119
120    @Override
121    public boolean equals(Object o) {
122        return EqualsBuilder.reflectionEquals(this, o);
123    }
124
125    @Override
126    public String toString() {
127        return ToStringBuilder.reflectionToString(this);
128    }
129
130    public void setBatchSize(int batchSize) {
131        this.batchSize = batchSize;
132    }
133
134    public void setBucketSize(int bucketSize) {
135        this.bucketSize = bucketSize;
136    }
137
138    public void setRepository(String repository) {
139        this.repository = repository;
140    }
141
142    public static class Builder {
143        protected final String action;
144
145        protected final String query;
146
147        protected String repository;
148
149        protected String username;
150
151        protected int bucketSize;
152
153        protected int batchSize;
154
155        protected Map<String, Serializable> params = new HashMap<>();
156
157        /**
158         * BulkCommand builder
159         *
160         * @param action the registered bulk action name
161         * @param nxqlQuery the query that represent the document set to apply the action
162         */
163        public Builder(String action, String nxqlQuery) {
164            if (isEmpty(action)) {
165                throw new IllegalArgumentException("Action cannot be empty");
166            }
167            this.action = action;
168            if (isEmpty(nxqlQuery)) {
169                throw new IllegalArgumentException("Query cannot be empty");
170            }
171            this.query = nxqlQuery;
172        }
173
174        /**
175         * Use a non default document repository
176         */
177        public Builder repository(String name) {
178            this.repository = name;
179            return this;
180        }
181
182        /**
183         * User running the bulk action
184         */
185        public Builder user(String name) {
186            this.username = name;
187            return this;
188        }
189
190        /**
191         * The size of a bucket of documents id that fits into a record
192         */
193        public Builder bucket(int size) {
194            if (size <= 0) {
195                throw new IllegalArgumentException("Invalid bucket size must > 0");
196            }
197            if (batchSize > size) {
198                throw new IllegalArgumentException(
199                        String.format("Bucket size: %d must be greater or equals to batch size: %d", size, batchSize));
200            }
201            this.bucketSize = size;
202            return this;
203        }
204
205        /**
206         * The number of documents processed by action within a transaction
207         */
208        public Builder batch(int size) {
209            if (size <= 0) {
210                throw new IllegalArgumentException("Invalid batch size must > 0");
211            }
212            if (bucketSize > 0 && size > bucketSize) {
213                throw new IllegalArgumentException(
214                        String.format("Bucket size: %d must be greater or equals to batch size: %d", size, batchSize));
215            }
216            this.batchSize = size;
217            return this;
218        }
219
220        /**
221         * Add an action parameter
222         */
223        public Builder param(String key, Serializable value) {
224            if (isEmpty(key)) {
225                throw new IllegalArgumentException("Param key cannot be null");
226            }
227            params.put(key, value);
228            return this;
229        }
230
231        /**
232         * Set all action parameters
233         */
234        public Builder params(Map<String, Serializable> params) {
235            if (params != null && !params.isEmpty()) {
236                if (params.containsKey(null)) {
237                    throw new IllegalArgumentException("Param key cannot be null");
238                }
239                this.params = params;
240            }
241            return this;
242        }
243
244        public BulkCommand build() {
245            return new BulkCommand(this);
246        }
247
248    }
249}