001package gu.sql2java; 002 003import java.io.ByteArrayInputStream; 004import java.io.IOException; 005import java.io.InputStream; 006import java.lang.reflect.InvocationTargetException; 007import java.net.MalformedURLException; 008import java.net.URL; 009import java.net.URLConnection; 010import java.net.URLStreamHandler; 011import java.nio.ByteBuffer; 012import java.util.Map; 013 014import com.google.common.base.Strings; 015import com.google.common.base.Throwables; 016 017import gu.sql2java.exception.ObjectRetrievalException; 018import gu.sql2java.store.BaseURLStore; 019import gu.sql2java.store.ConditionChecks; 020import gu.sql2java.store.DataNotFoundException; 021 022import static com.google.common.base.URLParseChecks.checkURLParse; 023import static com.google.common.base.Preconditions.*; 024 025import static gu.sql2java.store.BinaryUtils.*; 026import static gu.sql2java.store.ConditionChecks.checkNotNull; 027import static gu.sql2java.store.ConditionChecks.checkTrue; 028 029public class BaseColumnStore extends BaseURLStore { 030 031 private final Handler handler = new Handler(); 032 033 protected final String tablename; 034 035 protected final String storeColumn; 036 037 protected final TableManager<BaseBean> manager; 038 039 protected final RowMetaData metaData; 040 041 protected final Class<?> storeColumnType; 042 043 protected final String protocol; 044 045 protected final int extensionId; 046 047 protected final int mimeId; 048 049 protected final static String PKS = "PKS"; 050 051 public BaseColumnStore(String tablename,String storeColumn, String extensionColumn, String mimeColumn) { 052 this.tablename = tablename; 053 this.storeColumn = storeColumn; 054 this.manager = Managers.managerOf(tablename); 055 this.metaData = RowMetaData.getMetaData(tablename); 056 this.extensionId = metaData.columnIDOf(extensionColumn); 057 this.mimeId = metaData.columnIDOf(mimeColumn); 058 this.storeColumnType = metaData.columnTypeOf(storeColumn); 059 checkArgument(storeColumnType != null, "INVALID column %s", storeColumn); 060 checkArgument(byte[].class.equals(storeColumnType) || ByteBuffer.class.isAssignableFrom(storeColumnType), 061 "INVALID column type of %s,byte[] or java.nio.ByteBuffer required",storeColumn); 062 checkArgument(extensionColumn == null || String.class.equals(metaData.columnTypeOf(this.extensionId)), 063 "INVALID extensionColumn %s",extensionColumn); 064 checkArgument(mimeColumn == null || String.class.equals(metaData.columnTypeOf(this.mimeId)), 065 "INVALID mimeColumn %s",mimeColumn); 066 this.protocol = tablename.replaceAll("_", "-"); 067 } 068 069 @Override 070 public String getProtocol() { 071 return protocol; 072 } 073 protected Object[] primaryKeysOf(String md5, String extension){ 074 Map<String, Object> p = additionalParams.get(); 075 checkState(p != null && p.containsKey(PKS), "NOT PK defined"); 076 Object v = p.get(PKS); 077 checkState(v instanceof Object[],"INVALID PK type,Object[] required"); 078 return (Object[])v; 079 } 080 protected final String pathOf(String suffix, Object... primaryKeys){ 081 StringBuffer buffer = new StringBuffer(storeColumn); 082 for(int i=0; i < primaryKeys.length;++i){ 083 buffer.append("/").append(primaryKeys[i]); 084 } 085 if(!Strings.isNullOrEmpty(suffix)){ 086 buffer.append('.').append(suffix); 087 } 088 return buffer.toString(); 089 } 090 protected final URL makeURL(String suffix, Object... primaryKeys){ 091 try { 092 return new URL(getProtocol(),null,-1,pathOf(suffix,primaryKeys)); 093 } catch (MalformedURLException e) { 094 throw new RuntimeException(e); 095 } 096 } 097 /** 098 * 从数据库记录中获取当前数据的扩展名 099 * @param bean 数据库记录(不为{@code null}) 100 * @return 扩展名 101 */ 102 protected String getExtension(BaseBean bean) { 103 return bean.getValue(extensionId); 104 } 105 106 /** 107 * 从数据库记录中获取当前数据的MIME类型 108 * @param bean 数据库记录(不为{@code null}) 109 * @return MIME类型 110 */ 111 protected String getMime(BaseBean bean) { 112 String mime = bean.getValue(mimeId); 113 String extension = bean.getValue(extensionId); 114 if(mime != null){ 115 return mime; 116 } else if( extension != null){ 117 mime = URLConnection.guessContentTypeFromName("." + extension); 118 if(mime != null){ 119 return mime; 120 } 121 } 122 return guessContentType(bean); 123 } 124 125 private String guessContentType(BaseBean bean) { 126 try { 127 if(bean == null){ 128 return null; 129 } 130 byte[] data = getBytes(bean.getValue(storeColumn)); 131 if(data == null){ 132 return null; 133 } 134 ByteArrayInputStream is = new ByteArrayInputStream(data); 135 return URLConnection.guessContentTypeFromStream(is); 136 } catch (IOException e) { 137 throw new RuntimeException(e); 138 } 139 } 140 141 protected final void fillStoreBean(BaseBean bean, byte[] binary, String extension) { 142 if(ByteBuffer.class.equals(storeColumnType)){ 143 bean.setValue(storeColumn, ByteBuffer.wrap(binary)); 144 }else{ 145 bean.setValue(storeColumn, binary); 146 } 147 if(extensionId >= 0){ 148 bean.setValue(extensionId, extension); 149 } 150 if(mimeId >= 0){ 151 bean.setValue(mimeId, getMime(bean)); 152 } 153 } 154 155 156 @Override 157 protected URL doStore(byte[] binary, String md5, String extension, boolean makeURLOnly) throws IOException { 158 try { 159 Object[] pks = primaryKeysOf(md5,extension); 160 BaseBean bean = manager.loadByPrimaryKeyChecked(pks); 161 fillStoreBean(bean, binary, extension); 162 if(!makeURLOnly){ 163 manager.save(bean); 164 } 165 return makeURL(extension, pks); 166 } catch (Exception e) { 167 Throwables.throwIfInstanceOf(e, IOException.class); 168 throw new IOException(e); 169 } finally{ 170 additionalParams.remove(); 171 } 172 } 173 174 @Override 175 protected URL doFind(String md5) { 176 return null; 177 } 178 179 @Override 180 protected boolean doExists(URL storedURL) { 181 DatabaseURLConnection connection = new DatabaseURLConnection(storedURL).parse(); 182 BaseBean found = manager.loadByPrimaryKey(connection.primaryKeys); 183 return found != null && found.getValue(storeColumn) != null; 184 } 185 186 @Override 187 protected boolean doDelete(URL storedURL) throws IOException { 188 try { 189 DatabaseURLConnection bean = new DatabaseURLConnection(storedURL).parse(); 190 return manager.deleteByPrimaryKey(bean.primaryKeys) == 1; 191 } catch (Exception e) { 192 Throwables.throwIfInstanceOf(e, IOException.class); 193 throw new IOException(e); 194 } 195 } 196 197 @Override 198 protected URLStreamHandler doGetURLStreamHandler() { 199 return handler; 200 } 201 202 @Override 203 public int hashCode() { 204 final int prime = 31; 205 int result = super.hashCode(); 206 result = prime * result + extensionId; 207 result = prime * result + mimeId; 208 result = prime * result + ((protocol == null) ? 0 : protocol.hashCode()); 209 result = prime * result + ((storeColumn == null) ? 0 : storeColumn.hashCode()); 210 result = prime * result + ((tablename == null) ? 0 : tablename.hashCode()); 211 return result; 212 } 213 214 @Override 215 public boolean equals(Object obj) { 216 if (this == obj) 217 return true; 218 if (!super.equals(obj)) 219 return false; 220 if (!(obj instanceof BaseColumnStore)) 221 return false; 222 BaseColumnStore other = (BaseColumnStore) obj; 223 if (extensionId != other.extensionId) 224 return false; 225 if (mimeId != other.mimeId) 226 return false; 227 if (protocol == null) { 228 if (other.protocol != null) 229 return false; 230 } else if (!protocol.equals(other.protocol)) 231 return false; 232 if (storeColumn == null) { 233 if (other.storeColumn != null) 234 return false; 235 } else if (!storeColumn.equals(other.storeColumn)) 236 return false; 237 if (tablename == null) { 238 if (other.tablename != null) 239 return false; 240 } else if (!tablename.equals(other.tablename)) 241 return false; 242 return true; 243 } 244 245 @Override 246 public String toString() { 247 StringBuilder builder = new StringBuilder(); 248 builder.append(getClass().getSimpleName() + " [tablename="); 249 builder.append(tablename); 250 builder.append(", storeColumn="); 251 builder.append(storeColumn); 252 builder.append(", extensionColumn="); 253 builder.append(metaData.columnNameOf(extensionId)); 254 builder.append(", mimeColumn="); 255 builder.append(metaData.columnNameOf(mimeId)); 256 builder.append(", protocol="); 257 builder.append(protocol); 258 builder.append("]"); 259 return builder.toString(); 260 } 261 262 protected class Handler extends URLStreamHandler{ 263 264 @Override 265 protected URLConnection openConnection(URL u) throws IOException { 266 return new DatabaseURLConnection(u); 267 } 268 269 } 270 protected class DatabaseURLConnection extends URLConnection{ 271 272 Object[] primaryKeys; 273 274 protected DatabaseURLConnection(URL url) { 275 super(url); 276 } 277 /** 278 * 解析URL为数据访问信息<br> 279 * 要求的URL格式 ${tablename}://${storeColumn}/${pk}...[/${pkN}] 280 * @return 281 * @throws URLParseException 282 */ 283 DatabaseURLConnection parse() throws URLParseException{ 284 checkTrue(getProtocol().equals(getURL().getProtocol()), URLParseException.class, 285 "INVALID protocol ,%s reqired",getProtocol()); 286 String[] components = url.getPath().split("/"); 287 try { 288 checkTrue(components[0].equals(storeColumn), URLParseException.class, 289 "INVALID URL,the URL's path be reqired to start with '%s'",storeColumn); 290 checkTrue(components.length == metaData.primaryKeyCount+1, URLParseException.class, 291 "MISMATCH path components acount, %s required",metaData.primaryKeyCount+1); 292 String last = components[components.length-1]; 293 int lastDot = last.lastIndexOf("."); 294 if(lastDot >= 0){ 295 // remove suffix if exists 296 components[components.length-1] = last.substring(0, lastDot); 297 } 298 primaryKeys = new Object[metaData.primaryKeyTypes.length]; 299 for(int i = 0; i < metaData.primaryKeyTypes.length;++i){ 300 primaryKeys[i] = valueOf(components[i+1], metaData.primaryKeyTypes[i]); 301 } 302 } catch (IndexOutOfBoundsException e) { 303 throw new URLParseException("URL's path lack the necessary components /${storeColumn}/${pk}...[/${pkN}]", e); 304 } catch (StringCastException e) { 305 throw new URLParseException("FAIL TO get primary key from URL's path", e); 306 } 307 checkURLParse(url != null, "NOT DEFINED store column name in ref(#)"); 308 Class<?> columnType = checkNotNull(metaData.columnTypeOf(storeColumn),URLParseException.class,"INVALID column %s",storeColumn); 309 checkURLParse(byte[].class.equals(columnType) || ByteBuffer.class.isAssignableFrom(columnType), 310 "INVALID column type of %s, binary type ( byte[] or java.nio.ByteBuffer) required",storeColumn); 311 return this; 312 } 313 314 @Override 315 public void connect() throws IOException { 316 try { 317 parse(); 318 connected = true; 319 } catch (Exception e) { 320 Throwables.throwIfInstanceOf(e, IOException.class); 321 throw new IOException(e); 322 } 323 } 324 @Override 325 public InputStream getInputStream() throws IOException{ 326 connect(); 327 try { 328 BaseBean bean = manager.loadByPrimaryKeyChecked(primaryKeys); 329 Object data = bean.getValue(storeColumn); 330 ConditionChecks.checkTrue(data != null, DataNotFoundException.class, "column %s of %s is null", storeColumn, url.toString()); 331 return new ByteArrayInputStream(getBytes(data)); 332 } catch (ObjectRetrievalException e) { 333 throw new DataNotFoundException(url,e); 334 }catch (Exception e) { 335 Throwables.throwIfInstanceOf(e, IOException.class); 336 throw new IOException(e); 337 } 338 } 339 } 340 @SuppressWarnings("unchecked") 341 private static <T> T valueOf(String input,Class<T> targetType)throws StringCastException{ 342 if(Strings.isNullOrEmpty(input) || String.class.equals(targetType)){ 343 return (T) input; 344 } 345 checkNotNull(targetType != null, StringCastException.class,"target is null"); 346 try { 347 return (T) targetType.getMethod("valueOf", String.class).invoke(null, input); 348 } catch (IllegalAccessException | NoSuchMethodException | SecurityException e) { 349 } catch (InvocationTargetException e) { 350 throw new StringCastException(e.getTargetException()); 351 } 352 throw new StringCastException(String.format("INVALID target type %s",targetType)); 353 } 354 public static class URLParseException extends RuntimeException{ 355 private static final long serialVersionUID = 1L; 356 357 public URLParseException(Throwable cause) { 358 super(cause); 359 } 360 361 public URLParseException(String message, Throwable cause) { 362 super(message, cause); 363 } 364 365 public URLParseException(String message) { 366 super(message); 367 } 368 } 369 public static class StringCastException extends RuntimeException{ 370 private static final long serialVersionUID = 1L; 371 372 public StringCastException(Throwable cause) { 373 super(cause); 374 } 375 376 public StringCastException(String message) { 377 super(message); 378 } 379 } 380} 381