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