Storage.java 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. * Password Management Servlets (PWM)
  3. * http://www.pwm-project.org
  4. *
  5. * Copyright (c) 2006-2009 Novell, Inc.
  6. * Copyright (c) 2009-2021 The PWM Project
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. package password.pwm.receiver;
  21. import jetbrains.exodus.ArrayByteIterable;
  22. import jetbrains.exodus.ByteIterable;
  23. import jetbrains.exodus.bindings.StringBinding;
  24. import jetbrains.exodus.env.Cursor;
  25. import jetbrains.exodus.env.Environment;
  26. import jetbrains.exodus.env.EnvironmentConfig;
  27. import jetbrains.exodus.env.Environments;
  28. import jetbrains.exodus.env.Store;
  29. import jetbrains.exodus.env.StoreConfig;
  30. import jetbrains.exodus.env.Transaction;
  31. import password.pwm.bean.TelemetryPublishBean;
  32. import password.pwm.util.json.JsonFactory;
  33. import password.pwm.util.java.StringUtil;
  34. import java.io.File;
  35. import java.io.IOException;
  36. import java.time.Instant;
  37. import java.util.Iterator;
  38. public class Storage
  39. {
  40. private static final Logger LOGGER = Logger.createLogger( Storage.class );
  41. private static final String STORE_NAME = "store1";
  42. private final Environment environment;
  43. private Store store;
  44. public Storage( final Settings settings ) throws IOException
  45. {
  46. final String path = settings.getSetting( Settings.Setting.storagePath );
  47. if ( path == null )
  48. {
  49. throw new IOException( "data path is not specified!" );
  50. }
  51. final File dataPath = new File( path );
  52. if ( !dataPath.exists() )
  53. {
  54. throw new IOException( "data path '" + dataPath + "' does not exist" );
  55. }
  56. final File storagePath = new File( dataPath.getAbsolutePath() + File.separator + "storage" );
  57. mkdirs( storagePath );
  58. final EnvironmentConfig environmentConfig = new EnvironmentConfig();
  59. environment = Environments.newInstance( storagePath.getAbsolutePath(), environmentConfig );
  60. LOGGER.info( "environment open" );
  61. environment.executeInTransaction( txn -> store
  62. = environment.openStore( STORE_NAME, StoreConfig.WITHOUT_DUPLICATES, txn ) );
  63. LOGGER.info( "store open with " + count() + " records" );
  64. }
  65. public void store( final TelemetryPublishBean bean )
  66. {
  67. if ( bean == null )
  68. {
  69. return;
  70. }
  71. final String instanceHash = bean.getInstanceHash();
  72. if ( instanceHash != null )
  73. {
  74. final TelemetryPublishBean existingBean = get( instanceHash );
  75. Instant existingTimestamp = null;
  76. if ( existingBean != null )
  77. {
  78. existingTimestamp = existingBean.getTimestamp();
  79. }
  80. if ( existingTimestamp == null || existingTimestamp.isBefore( bean.getTimestamp() ) )
  81. {
  82. put( bean );
  83. }
  84. }
  85. }
  86. public Iterator<TelemetryPublishBean> iterator( )
  87. {
  88. return new InnerIterator();
  89. }
  90. private void put( final TelemetryPublishBean value )
  91. {
  92. environment.executeInTransaction( transaction ->
  93. {
  94. final ByteIterable k = StringBinding.stringToEntry( value.getInstanceHash() );
  95. final ByteIterable v = StringBinding.stringToEntry( JsonFactory.get().serialize( value ) );
  96. store.put( transaction, k, v );
  97. } );
  98. }
  99. private TelemetryPublishBean get( final String hash )
  100. {
  101. return environment.computeInTransaction( transaction ->
  102. {
  103. final ByteIterable k = StringBinding.stringToEntry( hash );
  104. final ByteIterable v = store.get( transaction, k );
  105. if ( v != null )
  106. {
  107. final String string = StringBinding.entryToString( new ArrayByteIterable( v ) );
  108. if ( StringUtil.notEmpty( string ) )
  109. {
  110. return JsonFactory.get().deserialize( string, TelemetryPublishBean.class );
  111. }
  112. }
  113. return null;
  114. } );
  115. }
  116. public void close( )
  117. {
  118. store.getEnvironment().close();
  119. }
  120. public long count( )
  121. {
  122. return environment.computeInTransaction( transaction -> store.count( transaction ) );
  123. }
  124. private class InnerIterator implements AutoCloseable, Iterator<TelemetryPublishBean>
  125. {
  126. private final Transaction transaction;
  127. private final Cursor cursor;
  128. private boolean closed;
  129. private String nextValue = "";
  130. InnerIterator( )
  131. {
  132. this.transaction = environment.beginReadonlyTransaction();
  133. this.cursor = store.openCursor( transaction );
  134. doNext();
  135. }
  136. private void doNext( )
  137. {
  138. try
  139. {
  140. if ( closed )
  141. {
  142. return;
  143. }
  144. if ( !cursor.getNext() )
  145. {
  146. close();
  147. return;
  148. }
  149. final ByteIterable nextKey = cursor.getKey();
  150. final String string = StringBinding.entryToString( new ArrayByteIterable( nextKey ) );
  151. if ( string == null || string.isEmpty() )
  152. {
  153. close();
  154. return;
  155. }
  156. nextValue = string;
  157. }
  158. catch ( final Exception e )
  159. {
  160. e.printStackTrace();
  161. throw e;
  162. }
  163. }
  164. @Override
  165. public void close( )
  166. {
  167. if ( closed )
  168. {
  169. return;
  170. }
  171. cursor.close();
  172. transaction.abort();
  173. nextValue = null;
  174. closed = true;
  175. }
  176. @Override
  177. public boolean hasNext( )
  178. {
  179. return !closed && nextValue != null;
  180. }
  181. @Override
  182. public TelemetryPublishBean next( )
  183. {
  184. final String value = nextValue;
  185. doNext();
  186. return get( value );
  187. }
  188. @Override
  189. public void remove( )
  190. {
  191. throw new UnsupportedOperationException( "remove not supported" );
  192. }
  193. }
  194. static void mkdirs( final File file ) throws IOException
  195. {
  196. if ( file.exists() )
  197. {
  198. if ( file.isDirectory() )
  199. {
  200. return;
  201. }
  202. throw new IOException( "path already exists as file: " + file.getAbsolutePath() );
  203. }
  204. if ( !file.mkdirs() )
  205. {
  206. throw new IOException( "unable to create path " + file.getAbsolutePath() );
  207. }
  208. }
  209. }