1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.shiro.hazelcast.cache;
20
21 import com.hazelcast.config.Config;
22 import com.hazelcast.core.Hazelcast;
23 import com.hazelcast.core.HazelcastInstance;
24 import org.apache.shiro.lang.ShiroException;
25 import org.apache.shiro.cache.Cache;
26 import org.apache.shiro.cache.CacheException;
27 import org.apache.shiro.cache.CacheManager;
28 import org.apache.shiro.cache.MapCache;
29 import org.apache.shiro.lang.util.Destroyable;
30 import org.apache.shiro.lang.util.Initializable;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import java.util.Map;
35
36 /**
37 * A {@code CacheManager} implementation backed by <a href="http://www.hazelcast.com/">Hazelcast</a>,
38 * "an open source clustering and highly scalable data distribution platform for Java"
39 * <p/>
40 * This implementation interacts with a {@link HazelcastInstance} to
41 * {@link HazelcastInstance#getMap(String) acquire} named {@link java.util.concurrent.ConcurrentMap ConcurrentMap}
42 * instances. Those clustered/distributed Map instances are then wrapped and made available to {@code CacheManager}
43 * callers as {@link MapCache} instances via {@link #getCache(String)}.
44 * <h2>Configuration</h2>
45 * This implementation's backing {@code HazelcastInstance} can be configured in one of three ways:
46 * <ol>
47 * <li>Doing nothing and leveraging default Hazelcast configuration mechanisms</li>
48 * <li>Supplying an already-existing {@code HazelcastInstance}</li>
49 * <li>Supplying a {@link Config} instance and using that to create a new {@code HazelcastInstance}</li>
50 * </ol>
51 * <h3>Default Configuration</h3>
52 * If you simply instantiate a {@code HazelcastCacheManager} and do nothing further, its backing
53 * {@link HazelcastInstance} instance will be created automatically by calling
54 * {@link Hazelcast#newHazelcastInstance(com.hazelcast.config.Config) Hazelcast.newHazelcastInstance(null)}.
55 * <p/>
56 * The null argument instructs Hazelcast to use whatever default configuration mechanism it has at its disposal,
57 * usually a {@code hazelcast.xml} file at the root of the classpath, or if that is not present, the
58 * {@code hazelcast-default.xml} file contained in the Hazelcast {@code .jar} file itself.
59 * <p/>
60 * <h3>An existing {@code HazelcastInstance}</h3>
61 * If you have created a {@code HazelcastInstance} outside of Shiro's knowledge/control, you can simply configure it
62 * to be used by calling {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) setHazelcastInstance}.
63 * <p/>
64 * <h3>A {@link Config} instance</h3>
65 * If you do not want to use the above two options, you can have programmatic control over all of Hazelcast's
66 * configuration by <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">creating and configuring a
67 * Config instance</a>.
68 * <p/>
69 * Once constructed, you can set it via {@link #setConfig(com.hazelcast.config.Config) setConfig(config)}. This config
70 * instance will be used to acquire a new Hazelcast instance by calling
71 * {@link Hazelcast#newHazelcastInstance(Config) Hazelcast.newHazelcastInstance(config)}
72 *
73 * @see <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">Hazelcast Configuration Documentation</a>
74 * @since 1.3
75 */
76 public class HazelcastCacheManager implements CacheManager, Initializable, Destroyable {
77
78 private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastCacheManager.class);
79
80 private boolean implicitlyCreated;
81 private HazelcastInstance hazelcastInstance;
82 private Config config;
83
84 /**
85 * Returns a {@link MapCache} instance representing the named Hazelcast-managed
86 * {@link com.hazelcast.core.IMap IMap}. The Hazelcast Map is obtained by calling
87 * {@link HazelcastInstance#getMap(String) hazelcastInstance.getMap(name)}.
88 *
89 * @param name the name of the cache to acquire.
90 * @param <K> the type of map key
91 * @param <V> the type of map value
92 * @return a {@link MapCache} instance representing the named Hazelcast-managed {@link com.hazelcast.core.IMap IMap}.
93 * @throws CacheException
94 * @see HazelcastInstance#getMap(String)
95 * @see #ensureHazelcastInstance()
96 */
97 public <K, V> Cache<K, V> getCache(String name) throws CacheException {
98 //returned map is a ConcurrentMap
99 Map<K, V> map = ensureHazelcastInstance().getMap(name);
100 return new MapCache<K, V>(name, map);
101 }
102
103 /**
104 * Ensures that this implementation has a backing {@link HazelcastInstance}, and if not, implicitly creates one
105 * via {@link #createHazelcastInstance()}.
106 *
107 * @return the backing (potentially newly created) {@code HazelcastInstance}.
108 * @see #createHazelcastInstance()
109 * @see HazelcastInstance
110 */
111 protected HazelcastInstance ensureHazelcastInstance() {
112 if (this.hazelcastInstance == null) {
113 this.hazelcastInstance = createHazelcastInstance();
114 this.implicitlyCreated = true;
115 }
116 return this.hazelcastInstance;
117 }
118
119 /**
120 * Initializes this instance by {@link #ensureHazelcastInstance() ensuring} there is a backing
121 * {@link HazelcastInstance}.
122 *
123 * @throws ShiroException
124 * @see #ensureHazelcastInstance()
125 * @see HazelcastInstance
126 */
127 public void init() throws ShiroException {
128 ensureHazelcastInstance();
129 }
130
131 /**
132 * Implicitly creates and returns a new {@link HazelcastInstance} that will be used to back this implementation.
133 * This implementation calls:
134 * <pre>
135 * return Hazelcast.newHazelcastInstance(this.config);
136 * </pre>
137 * using any {@link #setConfig(com.hazelcast.config.Config) configured} {@code Config} object. If no config
138 * object has been specified, {@code this.config} will be {@code null}, thereby using Hazelcast's
139 * <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">default configuration mechanism</a>.
140 * <p/>
141 * Can be overridden by subclasses for custom creation behavior.
142 *
143 * @return a new {@link HazelcastInstance} that will be used to back this implementation
144 * @see Hazelcast#newHazelcastInstance(com.hazelcast.config.Config)
145 * @see Config
146 */
147 protected HazelcastInstance createHazelcastInstance() {
148 return Hazelcast.newHazelcastInstance(this.config);
149 }
150
151 //needed for unit tests only - not part of Shiro's public API
152
153 /**
154 * NOT PART OF SHIRO'S ACCESSIBLE API. DO NOT DEPEND ON THIS. This method was added for testing purposes only.
155 * <p/>
156 * Returns {@code true} if this {@code HazelcastCacheManager} instance implicitly created the backing
157 * {@code HazelcastInstance}, or {@code false} if one was externally provided via
158 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) setHazelcastInstance}.
159 *
160 * @return {@code true} if this {@code HazelcastCacheManager} instance implicitly created the backing
161 * {@code HazelcastInstance}, or {@code false} if one was externally provided via
162 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) setHazelcastInstance}.
163 */
164 protected final boolean isImplicitlyCreated() {
165 return this.implicitlyCreated;
166 }
167
168 /**
169 * Destroys any {@link #ensureHazelcastInstance() implicitly created} backing {@code HazelcastInstance}. If the
170 * backing Hazelcast was not implicitly created (i.e. because it was configured externally and supplied via
171 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) setHazelcastInstance}), this method does
172 * nothing.
173 *
174 * @throws Exception if there is a problem shutting down
175 */
176 public void destroy() throws Exception {
177 if (this.implicitlyCreated) {
178 try {
179 this.hazelcastInstance.getLifecycleService().shutdown();
180 } catch (Throwable t) {
181 if (LOGGER.isWarnEnabled()) {
182 LOGGER.warn("Unable to cleanly shutdown implicitly created HazelcastInstance. "
183 + "Ignoring (shutting down)...", t);
184 }
185 } finally {
186 this.hazelcastInstance = null;
187 this.implicitlyCreated = false;
188 }
189 }
190 }
191
192 /**
193 * Returns the {@code HazelcastInstance} from which named {@link java.util.concurrent.ConcurrentMap ConcurrentMap}
194 * instances will be acquired to create {@link MapCache} instances.
195 *
196 * @return the {@code HazelcastInstance} from which named {@link java.util.concurrent.ConcurrentMap ConcurrentMap}
197 * instances will be acquired to create {@link MapCache} instances.
198 */
199 public HazelcastInstance getHazelcastInstance() {
200 return hazelcastInstance;
201 }
202
203 /**
204 * Sets the {@code HazelcastInstance} from which named {@link java.util.concurrent.ConcurrentMap ConcurrentMap}
205 * instances will be acquired to create {@link MapCache} instances.
206 *
207 * @param hazelcastInstance the {@code HazelcastInstance} from which named
208 * {@link java.util.concurrent.ConcurrentMap ConcurrentMap} instances will be acquired to create
209 * {@link MapCache} instances.
210 */
211 public void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
212 this.hazelcastInstance = hazelcastInstance;
213 }
214
215 /**
216 * Returns the Hazelcast {@code Config} object to use to create a backing {@code HazelcastInstance} if one is not
217 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) supplied}, or {@code null} if the
218 * default <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">Hazelcast configuration
219 * mechanisms</a> will be used.
220 *
221 * @return the Hazelcast {@code Config} object to use to create a backing {@code HazelcastInstance} if one is not
222 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) supplied}, or {@code null} if the
223 * default <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">Hazelcast configuration
224 * mechanisms</a> will be used.
225 * @see Hazelcast#newHazelcastInstance(com.hazelcast.config.Config)
226 */
227 public Config getConfig() {
228 return config;
229 }
230
231 /**
232 * Sets the Hazelcast {@code Config} object to use to create a backing {@code HazelcastInstance} if one is not
233 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) supplied}. {@code null} can be set if the
234 * default <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">Hazelcast configuration
235 * mechanisms</a> will be used.
236 *
237 * @param config the Hazelcast {@code Config} object to use to create a backing {@code HazelcastInstance} if one is not
238 * {@link #setHazelcastInstance(com.hazelcast.core.HazelcastInstance) supplied}, or {@code null} if the
239 * default <a href="http://www.hazelcast.com/docs/2.5/manual/multi_html/ch12.html">Hazelcast configuration
240 * mechanisms</a> will be used.
241 */
242 public void setConfig(Config config) {
243 this.config = config;
244 }
245
246 }