001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.impl.cluster;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.concurrent.locks.StampedLock;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.Ordered;
028import org.apache.camel.cluster.CamelClusterMember;
029import org.apache.camel.cluster.CamelClusterService;
030import org.apache.camel.cluster.CamelClusterView;
031import org.apache.camel.support.ServiceSupport;
032import org.apache.camel.util.ReferenceCount;
033import org.apache.camel.util.concurrent.LockHelper;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037public abstract class AbstractCamelClusterService<T extends CamelClusterView> extends ServiceSupport implements CamelClusterService {
038    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCamelClusterService.class);
039
040    private final Map<String, ViewHolder<T>> views;
041    private final Map<String, Object> attributes;
042    private final StampedLock lock;
043    private int order;
044    private String id;
045    private CamelContext camelContext;
046
047    protected AbstractCamelClusterService() {
048        this(null, null);
049    }
050
051    protected AbstractCamelClusterService(String id) {
052        this(id, null);
053    }
054
055    protected AbstractCamelClusterService(String id, CamelContext camelContext) {
056        this.order = Ordered.LOWEST;
057        this.id = id;
058        this.camelContext = camelContext;
059        this.views = new HashMap<>();
060        this.lock = new StampedLock();
061        this.attributes = new HashMap<>();
062    }
063
064    @Override
065    public int getOrder() {
066        return order;
067    }
068
069    public void setOrder(int order) {
070        this.order = order;
071    }
072
073    @Override
074    public void setId(String id) {
075        this.id = id;
076    }
077
078    @Override
079    public String getId() {
080        return id;
081    }
082
083    @Override
084    public void setCamelContext(CamelContext camelContext) {
085        this.camelContext = camelContext;
086
087        LockHelper.doWithWriteLock(
088            lock,
089            () -> {
090                for (ViewHolder<T> holder : views.values()) {
091                    holder.get().setCamelContext(camelContext);
092                }
093            }
094        );
095    }
096
097    @Override
098    public CamelContext getCamelContext() {
099        return camelContext;
100    }
101
102    public void setAttributes(Map<String, Object> attributes) {
103        this.attributes.clear();
104        this.attributes.putAll(attributes);
105    }
106
107    public void setAttribute(String key, Object value) {
108        this.attributes.put(key, value);
109    }
110
111    @Override
112    public Map<String, Object> getAttributes() {
113        return Collections.unmodifiableMap(attributes);
114    }
115
116    @Override
117    protected void doStart() throws Exception {
118        LockHelper.doWithReadLockT(
119            lock,
120            () -> {
121                for (ViewHolder<T> holder : views.values()) {
122                    holder.get().start();
123                }
124            }
125        );
126    }
127
128    @Override
129    protected void doStop() throws Exception {
130        LockHelper.doWithReadLockT(
131            lock,
132            () -> {
133                for (ViewHolder<T> holder : views.values()) {
134                    holder.get().stop();
135                }
136            }
137        );
138    }
139
140    @Override
141    public CamelClusterView getView(String namespace) throws Exception {
142        return LockHelper.callWithWriteLock(
143            lock,
144            () -> {
145                ViewHolder<T> holder = views.get(namespace);
146
147                if (holder == null) {
148                    T view = createView(namespace);
149                    view.setCamelContext(this.camelContext);
150
151                    holder = new ViewHolder<>(view);
152
153                    views.put(namespace, holder);
154                }
155
156                // Add reference and eventually start the route.
157                return holder.retain();
158            }
159        );
160    }
161
162    @Override
163    public void releaseView(CamelClusterView view) throws Exception {
164        LockHelper.doWithWriteLock(
165            lock,
166            () -> {
167                ViewHolder<T> holder = views.get(view.getNamespace());
168
169                if (holder != null) {
170                    holder.release();
171                }
172            }
173        );
174    }
175
176    @Override
177    public Collection<String> getNamespaces() {
178        return LockHelper.supplyWithReadLock(
179            lock,
180            () -> {
181                // copy the key set so it is not modifiable and thread safe
182                // thus a little inefficient.
183                return new HashSet<>(views.keySet());
184            }
185        );
186    }
187
188    @Override
189    public void startView(String namespace) throws Exception {
190        LockHelper.doWithWriteLockT(
191            lock,
192            () -> {
193                ViewHolder<T> holder = views.get(namespace);
194
195                if (holder != null) {
196                    LOGGER.info("Force start of view {}", namespace);
197                    holder.startView();
198                } else {
199                    LOGGER.warn("Error forcing start of view {}: it does not exist", namespace);
200                }
201            }
202        );
203    }
204
205    @Override
206    public void stopView(String namespace) throws Exception {
207        LockHelper.doWithWriteLockT(
208            lock,
209            () -> {
210                ViewHolder<T> holder = views.get(namespace);
211
212                if (holder != null) {
213                    LOGGER.info("Force stop of view {}", namespace);
214                    holder.stopView();
215                } else {
216                    LOGGER.warn("Error forcing stop of view {}: it does not exist", namespace);
217                }
218            }
219        );
220    }
221
222    @Override
223    public boolean isLeader(String namespace) {
224        return LockHelper.supplyWithReadLock(
225            lock,
226            () -> {
227                ViewHolder<T> holder = views.get(namespace);
228                if (holder != null) {
229                    CamelClusterMember member = holder.get().getLocalMember();
230                    if (member != null) {
231                        return member.isLeader();
232                    }
233                }
234
235                return false;
236            }
237        );
238    }
239
240    // **********************************
241    // Implementation
242    // **********************************
243
244    protected abstract T createView(String namespace) throws Exception;
245
246    // **********************************
247    // Helpers
248    // **********************************
249
250    private final class ViewHolder<V extends CamelClusterView> {
251        private final V view;
252        private final ReferenceCount count;
253
254        ViewHolder(V view) {
255            this.view = view;
256            this.count = ReferenceCount.on(
257                () -> {
258                    try {
259                        this.startView();
260                    } catch (Exception e) {
261                        throw new RuntimeException(e);
262                    }
263                },
264                () -> {
265                    try {
266                        this.stopView();
267                    } catch (Exception e) {
268                        throw new RuntimeException(e);
269                    }
270                });
271        }
272
273        V get() {
274            return view;
275        }
276
277        V retain() {
278            LOGGER.debug("Retain view {}, old-refs={}", view.getNamespace(), count.get());
279
280            count.retain();
281
282            return get();
283        }
284
285        void release() {
286            LOGGER.debug("Release view {}, old-refs={}", view.getNamespace(), count.get());
287
288            count.release();
289        }
290
291        void startView() throws Exception {
292            if (AbstractCamelClusterService.this.isRunAllowed()) {
293                LOGGER.debug("Start view {}", view.getNamespace());
294                view.start();
295            } else {
296                LOGGER.debug("Can't start view {} as cluster service is not running, view will be started on service start-up", view.getNamespace());
297            }
298        }
299
300        void stopView() throws Exception {
301            LOGGER.debug("Stop view {}", view.getNamespace());
302            view.stop();
303        }
304    }
305}