001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.examples.core.config.store;
014
015import java.util.*;
016import java.util.concurrent.*;
017
018import org.apache.juneau.common.internal.*;
019import org.apache.juneau.config.store.*;
020
021/**
022 * Example of a {@link ConfigStore} that uses a relational database as a backend.
023 *
024 * <p>
025 * This class provides a basic framework but requires implementing the following methods:
026 * <ul class='javatree'>
027 *    <li class='jm'>{@link #getDatabaseValue(String)}
028 *    <li class='jm'>{@link #exists(String)}
029 * </ul>
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032 * </ul>
033 */
034@SuppressWarnings({"resource","unused","javadoc"})
035public class SqlStore extends ConfigStore {
036
037   //-----------------------------------------------------------------------------------------------------------------
038   // Static
039   //-----------------------------------------------------------------------------------------------------------------
040
041   static final String
042      SQLSTORE_jdbcUrl = "SqlStore.jdbcUrl",
043      SQLSTORE_tableName = "SqlStore.tableName",
044      SQLSTORE_nameColumn = "SqlStore.nameColumn",
045      SQLSTORE_valueColumn = "SqlStore.valueColumn",
046      SQLSTORE_pollInterval = "SqlStore.pollInterval";
047
048
049   /**
050    * Instantiates a builder for this object.
051    *
052    * @return A new builder for this object.
053    */
054   public static Builder create() {
055      return new Builder();
056   }
057
058   //-----------------------------------------------------------------------------------------------------------------
059   // Builder
060   //-----------------------------------------------------------------------------------------------------------------
061
062   /**
063    * Builder class.
064    */
065   public static class Builder extends ConfigStore.Builder {
066
067      String jdbcUrl, tableName, nameColumn, valueColumn;
068      int pollInterval;
069
070      Builder() {
071         this.jdbcUrl = env(SQLSTORE_jdbcUrl, "jdbc:derby:mydb");
072         this.tableName = env(SQLSTORE_tableName, "config");
073         this.nameColumn = env(SQLSTORE_nameColumn, "name");
074         this.valueColumn = env(SQLSTORE_valueColumn, "value");
075         this.pollInterval = env(SQLSTORE_pollInterval, 600);  // Time in seconds.
076      }
077
078      public Builder jdbcUrl(String value) {
079         this.jdbcUrl = value;
080         return this;
081      }
082
083      public Builder tableName(String value) {
084         this.tableName = value;
085         return this;
086      }
087
088      public Builder nameColumn(String value) {
089         this.nameColumn = value;
090         return this;
091      }
092
093      public Builder valueColumn(String value) {
094         this.valueColumn = value;
095         return this;
096      }
097
098      public Builder pollInterval(int value) {
099         this.pollInterval = value;
100         return this;
101      }
102
103      @Override
104      public Builder copy() {
105         return null;
106      }
107
108      @Override
109      public SqlStore build() {
110         return build(SqlStore.class);
111      }
112   }
113
114   //-----------------------------------------------------------------------------------------------------------------
115   // Instance
116   //-----------------------------------------------------------------------------------------------------------------
117
118   private final String jdbcUrl;
119   private final String tableName, nameColumn, valueColumn;
120   private final Timer watcher;
121   private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
122
123   /**
124    * Constructor.
125    *
126    * @param builder The builder for this object.
127    */
128   protected SqlStore(Builder builder) {
129      super(builder);
130      this.jdbcUrl = builder.jdbcUrl;
131      this.tableName = builder.tableName;
132      this.nameColumn = builder.nameColumn;
133      this.valueColumn = builder.valueColumn;
134
135      int pollInterval = builder.pollInterval;
136
137      TimerTask timerTask = new TimerTask() {
138         @Override
139         public void run() {
140            SqlStore.this.poll();
141         }
142      };
143
144      this.watcher = new Timer("MyTimer");
145      watcher.scheduleAtFixedRate(timerTask, 0, pollInterval * 1000);
146   }
147
148   synchronized void poll() {
149
150      // Loop through all our entries and find the latest values.
151      cache.forEach((name,cacheContents) -> {
152         String newContents = getDatabaseValue(name);
153
154         // Change detected!
155         if (! cacheContents.equals(newContents))
156            update(name, newContents);
157      });
158   }
159
160   // Reads the value from the database.
161   protected String getDatabaseValue(String name) {
162      // Implement me!
163      return null;
164   }
165
166   @Override /* ConfigStore */
167   public boolean exists(String name) {
168      // Implement me!
169      return false;
170   }
171
172   @Override /* ConfigStore */
173   public synchronized String read(String name) {
174      String contents = cache.get(name);
175      if (contents == null) {
176         contents = getDatabaseValue(name);
177         update(name, contents);
178      }
179      return contents;
180   }
181
182   @Override /* ConfigStore */
183   public synchronized String write(String name, String expectedContents, String newContents) {
184
185      // This is a no-op.
186      if (StringUtils.eq(expectedContents, newContents))
187         return null;
188
189      String currentContents = read(name);
190
191      if (expectedContents != null && StringUtils.ne(currentContents, expectedContents))
192         return currentContents;
193
194      update(name, newContents);
195
196      // Success!
197      return null;
198   }
199
200   @Override /* ConfigStore */
201   public synchronized SqlStore update(String name, String newContents) {
202      cache.put(name, newContents);
203      super.update(name, newContents);  // Trigger any listeners.
204      return this;
205   }
206
207   @Override /* Closeable */
208   public synchronized void close() {
209      if (watcher != null)
210         watcher.cancel();
211   }
212}