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