001/* 002 * Copyright (c) 2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2016-07-19 06:26:44 +0000 (Tue, 19 Jul 2016) $' 007 * '$Revision: 34509 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030/** 031 * 032 */ 033package org.kepler.util; 034 035import java.io.BufferedReader; 036import java.io.File; 037import java.io.FileInputStream; 038import java.io.FileNotFoundException; 039import java.io.FileOutputStream; 040import java.io.IOException; 041import java.io.InputStream; 042import java.io.InputStreamReader; 043import java.io.ObjectInput; 044import java.io.ObjectInputStream; 045import java.io.ObjectOutputStream; 046import java.io.OutputStream; 047import java.io.OutputStreamWriter; 048import java.net.URL; 049import java.net.URLConnection; 050import java.net.URLEncoder; 051import java.util.Hashtable; 052import java.util.List; 053import java.util.Properties; 054import java.util.UUID; 055import java.util.Vector; 056 057import org.apache.commons.logging.Log; 058import org.apache.commons.logging.LogFactory; 059import org.kepler.build.modules.Module; 060import org.kepler.build.project.ProjectLocator; 061import org.kepler.configuration.ConfigurationManager; 062import org.kepler.configuration.ConfigurationProperty; 063import org.kepler.objectmanager.lsid.LSIDGenerator; 064 065/** 066 * This class contains an Authority and a Namespace to be used for uniquely 067 * generating KeplerLSIDs. 068 * 069 * @author Aaron Schultz 070 */ 071public class AuthNamespace { 072 private static final Log log = LogFactory.getLog(AuthNamespace.class 073 .getName()); 074 private static final boolean isDebugging = log.isDebugEnabled(); 075 076 /** 077 * The authority that assigned the namespace to this Kepler instance. 078 */ 079 private String _authority; 080 081 /** 082 * The namespace that uniquely identifies this Kepler instance along with 083 * the authority 084 */ 085 private String _namespace; 086 087 /** 088 * The list of configured authorities to try to get a unique authNamespace 089 * from. This is populated from the config.xml file. 090 */ 091 private Vector<String> _configuredAuthorities; 092 093 /** 094 * The filename where the authority and namesapce are stored. Store the 095 * AuthorizedNamespace file in the KEPLER directory, not the cache 096 * directory. 097 */ 098 private String _anFileName; 099 100 private boolean _initialized = false; 101 102 private final String _saveFileName = "AuthorizedNamespace"; 103 104 /** singleton instance **/ 105 private static AuthNamespace instance = null; 106 107 /** 108 * Constructor. 109 */ 110 protected AuthNamespace() { 111 112 File persistentDir = DotKeplerManager.getInstance() 113 .getPersistentModuleDirectory("core"); 114 _anFileName = persistentDir.toString(); 115 if (!_anFileName.endsWith(File.separator)) { 116 _anFileName += File.separator; 117 } 118 _anFileName += _saveFileName; 119 if (isDebugging) 120 log.debug(_anFileName); 121 _configuredAuthorities = new Vector<String>(1); 122 123 // make sure the userDirPath exists 124 new File(DotKeplerManager.getDotKeplerPath()).mkdirs(); 125 126 } 127 128 /** 129 * @return the Authority 130 */ 131 public String getAuthority() { 132 return _authority; 133 } 134 135 private void setAuthority(String authority) throws Exception { 136 int ind; 137 if (authority.startsWith("http://")) { 138 authority = authority.substring(7); 139 } else if (authority.startsWith("https://")) { 140 authority = authority.substring(8); 141 } else if ( (ind = authority.indexOf("://")) >= 0) { 142 authority = authority.substring(ind+3); 143 } 144 if (authority.indexOf(':') >= 0) { 145 throw new Exception("authority can not contain a colon"); 146 } 147 _authority = authority; 148 } 149 150 /** 151 * @return the Namespace 152 */ 153 public String getNamespace() { 154 return _namespace; 155 } 156 157 private void setNamespace(String namespace) throws Exception { 158 if (namespace.indexOf(':') >= 0) { 159 throw new Exception("namespace can not contain a colon"); 160 } 161 _namespace = namespace; 162 } 163 164 public String getAuthNamespace() { 165 return getAuthority() + ":" + getNamespace(); 166 } 167 168 /** 169 * The purpose here is to return a unique string based on the authNamespace 170 * string that can be used for including in the name of Instance specific 171 * strings such as the .kepler directory name. This is a one way operation 172 * such that the HashValue can be obtained from the AuthNamespace string but 173 * probably not the other way around. 174 * 175 */ 176 public String getFileNameForm() { 177 String filenameFriendly = getAuthNamespace(); 178 filenameFriendly = filenameFriendly.replaceAll("/", "."); 179 filenameFriendly = filenameFriendly.replaceAll(":", "."); 180 filenameFriendly = filenameFriendly.replaceAll("\\.\\.", "."); 181 // log.debug(filenameFriendly); 182 return filenameFriendly; 183 } 184 185 /** 186 * Set the unique Authority and Namespace for this Kepler Instance. 187 */ 188 public void initialize() { 189 log.debug("initialize()"); 190 if (_initialized) 191 return; 192 _initialized = true; // never do this twice. 193 194 /** 195 * Configure the list of authNamespace services that should be used to 196 * get a unique namespace from. 197 */ 198 readAuthNamespaceServicesConfiguration(); 199 200 /* 201 * If the AuthNamespace file does not exist, create it. 202 */ 203 File ianFile = new File(_anFileName); 204 if (!ianFile.exists()) { 205 generateAuthNamespace(); 206 } 207 208 refreshAuthNamespace(); 209 210 if (getAuthority() == "uuid") { 211 /* 212 * TODO warn the user they are working with a uuid and ask if they'd 213 * like to verify a new namespace from an authority. 214 * 215 * This should be farmed out to a class in the gui module. If there 216 * is no gui around then an alternate path should be created for 217 * doing this. 218 */ 219 } 220 } 221 222 /** 223 * Don't use this method. Only if the LSID_GENERATOR table is corrupt 224 * or deleted do we want to get a new Authorized Namespace. 225 */ 226 public void getNewAuthorizedNamespace() { 227 File anFile = new File(_anFileName); 228 anFile.delete(); 229 generateAuthNamespace(); 230 refreshAuthNamespace(); 231 } 232 233 /** 234 * This initializer method is only to be used for JUnit testing. 235 */ 236 public void initializeForTesting(String authority, String namespace) { 237 log.debug("initializeForTests()"); 238 if (_initialized) 239 return; 240 _initialized = true; 241 242 // use a different file for testing 243 _anFileName = ProjectLocator.getProjectDir().toString(); 244 if (!_anFileName.endsWith(File.separator)) { 245 _anFileName += File.separator; 246 } 247 _anFileName += "AuthorizedNamespaceTesting"; 248 if (isDebugging) 249 log.debug(_anFileName); 250 251 // use a generic authority and namespace for testing 252 _authority = authority; 253 _namespace = namespace; 254 255 try { 256 writeAuthorizedNamespace(); 257 } catch (Exception e) { 258 System.out.println(e.toString()); 259 e.printStackTrace(); 260 } 261 _initialized = true; 262 } 263 264 /** 265 * Populate the configuredAuthorities list from the config.xml file. 266 */ 267 private void readAuthNamespaceServicesConfiguration() { 268 ConfigurationManager cm = ConfigurationManager.getInstance(); 269 Module cmMod = ConfigurationManager.getModule("configuration-manager"); 270 String path = "authNamespaceServices.authNamespaceService"; 271 List<ConfigurationProperty> anProps = cm.getProperties(cmMod, 272 path); 273 Hashtable<Integer,String> addys = new Hashtable<Integer,String>(); 274 for (ConfigurationProperty anProp : anProps) { 275 ConfigurationProperty ordProp = anProp.getProperty("ordering"); 276 ConfigurationProperty urlProp = anProp.getProperty("url"); 277 278 String ordStr = ordProp.getValue(); 279 Integer ordInt = Integer.parseInt(ordStr); 280 String urlStr = urlProp.getValue(); 281 282 addys.put(ordInt,urlStr); 283 } 284 for (int i = 1; i <= addys.size(); i++) { 285 Integer I = new Integer(i); 286 String addy = addys.get(I); 287 if (isDebugging) log.debug(addy); 288 _configuredAuthorities.add(addy); 289 } 290 } 291 292 /** 293 * Try to query the configured authorities to get an assigned namespace. 294 * Otherwise generate a uuid as the namespace. 295 */ 296 private void generateAuthNamespace() { 297 boolean verified = false; 298 299 /* 300 * First, try to connect to a server to get a verified namespace. 301 */ 302 try { 303 304 for (int i = 0; i < _configuredAuthorities.size(); i++) { 305 String currentAuthority = _configuredAuthorities.get(i); 306 verified = queryAuthorizedNamespace(currentAuthority); 307 if (_authority == null || _namespace == null) { 308 verified = false; 309 } 310 if (verified) { 311 log.info("A unique namespace, " + _namespace 312 + ", was successfully retrieved from authority " 313 + _authority); 314 break; 315 } 316 } 317 318 } catch (Exception e1) { 319 log.error(e1.toString()); 320 e1.printStackTrace(); 321 } 322 323 /* 324 * If a verified namespace cannot be retrieved, generate one. 325 */ 326 if (!verified) { 327 /* 328 * Generate a probabilistically unique random ID for the namespace 329 * and set uuid as the authority. 330 */ 331 log.warn("Generating UUID based AuthNamespace"); 332 333 try { 334 setAuthority("uuid"); 335 setNamespace(UUID.randomUUID().toString()); 336 337 writeAuthorizedNamespace(); 338 } catch (Exception e) { 339 log.error("Unable to write InstanceAuthNamespace file: " 340 + _anFileName); 341 e.printStackTrace(); 342 } 343 } 344 } 345 346 /** 347 * Read in the Authority and Namespace from the AuthorizedNamespace file. 348 */ 349 public void refreshAuthNamespace() { 350 if (isDebugging) 351 log.debug("refreshAuthNamespace()"); 352 353 try { 354 InputStream is = null; 355 ObjectInput oi = null; 356 try { 357 is = new FileInputStream(_anFileName); 358 oi = new ObjectInputStream(is); 359 Object newObj = oi.readObject(); 360 361 String theString = (String) newObj; 362 int firstColon = theString.indexOf(':'); 363 setAuthority(theString.substring(0, firstColon)); 364 setNamespace(theString.substring(firstColon + 1)); 365 } finally { 366 if(oi != null) { 367 oi.close(); 368 } 369 if(is != null) { 370 is.close(); 371 } 372 } 373 } catch (FileNotFoundException e) { 374 log.error(_saveFileName + " file was not found: " + _anFileName); 375 e.printStackTrace(); 376 } catch (IOException e) { 377 log.error("Unable to create ObjectInputStream"); 378 e.printStackTrace(); 379 } catch (ClassNotFoundException e) { 380 log.error("Unable to read from ObjectInput"); 381 e.printStackTrace(); 382 } catch (Exception e) { 383 log.error(e.toString()); 384 e.printStackTrace(); 385 } 386 387 if (isDebugging) { 388 log.debug("Instance Authority: " + getAuthority()); 389 log.debug("Instance Namespace: " + getNamespace()); 390 log.debug("Instance AuthNamespace: " + getAuthNamespace()); 391 log.debug("Instance Hash Value: " + getFileNameForm()); 392 } 393 394 } 395 396 /** 397 * Serialize the authority:namespace String to the AuthorizedNamespace file. 398 */ 399 private void writeAuthorizedNamespace() throws Exception { 400 if (isDebugging) 401 log.debug("writeAuthorizedNamespace()"); 402 403 File ianFile = new File(_anFileName); 404 if (!ianFile.exists()) { 405 ianFile.delete(); 406 } 407 String authNamespace = getAuthNamespace(); 408 if (authNamespace == null) { 409 throw new Exception("authNamespace is null"); 410 } 411 OutputStream os = new FileOutputStream(_anFileName); 412 ObjectOutputStream oos = null; 413 try { 414 oos = new ObjectOutputStream(os); 415 oos.writeObject(authNamespace); 416 oos.flush(); 417 } finally { 418 if(oos != null) { 419 oos.close(); 420 } 421 } 422 423 try { 424 // Every time we write a new AuthNamespace make sure there is 425 // at least one row added to the LSID_GENERATOR table for the 426 // new Authority and Namespace, this is how LSIDGenerator 427 // can tell if the LSID_GENERATOR table has been deleted since 428 // the last time it accessed it. 429 LSIDGenerator.insertRow(getAuthority(), getNamespace(), 0, 1); 430 } catch (Exception e) { 431 e.printStackTrace(); 432 } 433 } 434 435 /** 436 * Return a unique namespace from the provided authority string. 437 * 438 * @param authority 439 * @return boolean true if success 440 * @throws Exception 441 */ 442 private boolean queryAuthorizedNamespace(String authority) throws Exception { 443 if (isDebugging) 444 log.debug("queryAuthorizedNamespace( " + authority + ")"); 445 446 // thwart malicious robots 447 String data = URLEncoder.encode("username", "UTF-8") + "=" 448 + URLEncoder.encode("kepler", "UTF-8"); 449 data += "&" + URLEncoder.encode("password", "UTF-8") + "=" 450 + URLEncoder.encode("kepler", "UTF-8"); 451 452 try { 453 // Send the request 454 URL url = new URL(authority); 455 URLConnection conn = url.openConnection(); 456 conn.setDoOutput(true); 457 OutputStream os = conn.getOutputStream(); 458 OutputStreamWriter wr = new OutputStreamWriter(os); 459 wr.write(data); 460 wr.flush(); 461 462 // Get the response as a set of Properties 463 BufferedReader rd = new BufferedReader(new InputStreamReader(conn 464 .getInputStream())); 465 String line; 466 Properties props = new Properties(); 467 while ((line = rd.readLine()) != null) { 468 if (isDebugging) log.debug(line); 469 line = line.trim(); 470 if (line.equals("")) 471 continue; 472 int equalsIndex = line.indexOf("="); 473 if (equalsIndex > 0) { 474 String key = line.substring(0, equalsIndex).trim(); 475 String value = line.substring(equalsIndex + 1).trim(); 476 props.setProperty(key, value); 477 } 478 } 479 wr.close(); 480 rd.close(); 481 482 // Check to see if there was an error 483 String errorString = props.getProperty("error"); 484 if (errorString != null) { 485 log.warn(errorString); 486 } else { 487 // Check and set the returned namespace 488 String newNamespace = props.getProperty("namespace"); 489 if (newNamespace != null && !newNamespace.equals("")) { 490 setAuthority(authority); 491 setNamespace(newNamespace); 492 493 try { 494 writeAuthorizedNamespace(); 495 return true; 496 } catch (Exception e) { 497 log.error("Unable to write " + _saveFileName 498 + " file: " + _anFileName); 499 e.printStackTrace(); 500 } 501 } 502 } 503 } catch (Exception e) { 504 System.out.println(e.toString()); 505 } 506 507 return false; 508 } 509 510 /** 511 * Method for getting an instance of this singleton class. 512 */ 513 public static AuthNamespace getInstance() { 514 if (instance == null) { 515 instance = new AuthNamespace(); 516 instance.initialize(); 517 } 518 return instance; 519 } 520}