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