001/** 002 * Copyright 2015 Tampere University of Technology, Pori Department 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package service.tut.pori.apilta.files; 017 018import java.io.Closeable; 019import java.io.IOException; 020import java.util.Date; 021import java.util.Map; 022import java.util.Properties; 023import java.util.Set; 024 025import org.apache.log4j.Logger; 026import org.jclouds.Constants; 027import org.jclouds.ContextBuilder; 028import org.jclouds.http.HttpResponseException; 029import org.jclouds.io.Payload; 030import org.jclouds.openstack.swift.v1.SwiftApi; 031import org.jclouds.openstack.swift.v1.TemporaryUrlSigner; 032import org.jclouds.openstack.swift.v1.domain.SwiftObject; 033import org.jclouds.openstack.swift.v1.features.AccountApi; 034import org.jclouds.openstack.swift.v1.features.ContainerApi; 035import org.jclouds.openstack.swift.v1.features.ObjectApi; 036 037import core.tut.pori.context.ServiceInitializer; 038import core.tut.pori.http.Definitions; 039import service.tut.pori.apilta.ApiltaProperties; 040import service.tut.pori.apilta.files.datatypes.SignedURL; 041 042/** 043 * Template class for accessing a pre-configured swift instance 044 * 045 */ 046public class SwiftTemplate implements Closeable { 047 private static final long CHECK_API_INTERVAL = 1800L; 048 private static final Logger LOGGER = Logger.getLogger(SwiftTemplate.class); 049 private static final String PARAMETER_TEMP_URI_SIGNATURE = "temp_url_sig"; 050 private static final String PARAMETER_TEMP_URI_EXPIRATION = "temp_url_expires"; 051 private boolean _checkContainer = true; 052 private ObjectApi _objectApi = null; 053 private ApiltaProperties _properties = null; 054 private Set<String> _regions = null; 055 private SwiftApi _swiftApi = null; 056 private TemporaryUrlSigner _tempUriSigner = null; 057 058 /** 059 * 060 */ 061 public SwiftTemplate() { 062 initialize(); 063 } 064 065 /** 066 * 067 */ 068 private void initialize(){ 069 LOGGER.debug("Initializing Swift handler..."); 070 071 _properties = ServiceInitializer.getPropertyHandler().getSystemProperties(ApiltaProperties.class); 072 073 Properties overrides = new Properties(); 074 overrides.setProperty(Constants.PROPERTY_RETRY_DELAY_START, 500 + ""); 075 overrides.setProperty(Constants.PROPERTY_MAX_RETRIES, 5 + ""); 076 077 _swiftApi = ContextBuilder.newBuilder(_properties.getProvider()) 078 .endpoint(_properties.getEndpoint()) 079 .credentials(_properties.getIdentity(), _properties.getCredential()) 080 .overrides(overrides) 081 .buildApi(SwiftApi.class); 082 } 083 084 @Override 085 public void close() { 086 LOGGER.debug("Closing..."); 087 088 _tempUriSigner = null; 089 090 _objectApi = null; 091 try { 092 _swiftApi.close(); 093 } catch (IOException ex) { 094 LOGGER.error(ex, ex); 095 } 096 _swiftApi = null; 097 098 //reset members 099 _regions = null; 100 _checkContainer = true; 101 } 102 103 /** 104 * 105 * @param objectName 106 * @return details for the object matching the given GUID or null if object was not found 107 */ 108 public SwiftObject get(String objectName) { 109 return getObjectApi().getWithoutBody(objectName); //TODO add try-catch for connection refused situation, maybe try to change to another region? 110 } 111 112 /** 113 * 114 * @param objectName 115 */ 116 public void delete(String objectName) { 117 getObjectApi().delete(objectName); //TODO add try-catch for connection refused situation, maybe try to change to another region? 118 } 119 120 /** 121 * Returns the object for accessing files on Swift storage based on resolved region and configure container. 122 * @return Swift Object API 123 */ 124 private ObjectApi getObjectApi(){ 125 //TODO: the _objectApi (like any other swift API) might get stuck for some unknown reason. The best effort work around for this could just be re-instantiating the whole SwiftTemplate. 126 if(_objectApi == null){ //empty on first call as it is loaded on the first use of swift template 127 if(_checkContainer){ //just a safe guard to make sure required container exists 128 prepareContainer(); 129 } 130 _objectApi = _swiftApi.getObjectApi(resolveRegion(), _properties.getContainer()); 131 } 132 return _objectApi; 133 } 134 135 /** 136 * <p>Checks for the required container, and creates it if it doesn't exist.</p> 137 * <p>Make the method synchronized just in case.</p> 138 */ 139 private synchronized void prepareContainer(){ 140 _checkContainer = false; //disable future checks for container 141 //Make sure that the needed container exists 142 ContainerApi containerApi = _swiftApi.getContainerApi(resolveRegion()); 143 containerApi.create(_properties.getContainer()); //returns true if the container was created, false if the container already existed. 144 //regardless of the return value of the previous method call, the requested container should now be available 145 } 146 147 /** 148 * <p>This function returns the preferred region for this service instance if configured. 149 * Otherwise the first configured region is returned.</p> 150 * <p>Note that this function does not support multi region usage, i.e. the same selected region is used until the end of the world.</p> 151 * @return region name 152 */ 153 private String resolveRegion(){ 154 if(_regions == null){ 155 try{ 156 _regions = _swiftApi.getConfiguredRegions(); 157 }catch(HttpResponseException ex){ 158 LOGGER.error(ex, ex); 159 throw new RuntimeException("SwiftAPI refused connection."); 160 } 161 } 162 if(_regions.contains(_properties.getPreferredRegion())){ 163 return _properties.getPreferredRegion(); //return the preferred region, if available 164 }else{ 165 return _regions.iterator().next(); //return the first region in any other case (as a default choice) 166 } 167 } 168 169 /** 170 * 171 * @param objectName 172 * @param payload 173 * @return ETag of the object 174 */ 175 public String put(String objectName, Payload payload) { 176 return getObjectApi().put(objectName, payload); //TODO add try-catch for connection refused situation, maybe try to change to another region? 177 } 178 179 /** 180 * 181 * @param object 182 * @return temporary url signed by this template 183 */ 184 public SignedURL signURL(SwiftObject object) { 185 if(_tempUriSigner == null){ 186 AccountApi accountApi = _swiftApi.getAccountApi(resolveRegion()); 187 _tempUriSigner = TemporaryUrlSigner.checkApiEvery(accountApi, CHECK_API_INTERVAL); 188 } 189 long expires = System.currentTimeMillis() / 1000 + _properties.getDefaultExpirationTime(); 190 String signature = _tempUriSigner.sign(Definitions.METHOD_GET, object.getUri().getPath(), expires); 191 //TODO for some reason jclouds want to use public ip instead of internal ip when communicating service endpoints, which is kinda silly 192 //TODO Also this workaround defeats the point of having multiple regions (as it is hardcoded to a one endpoint). 193 return new SignedURL(_properties.getPublicEndpoint()+object.getUri().getPath()+Definitions.SEPARATOR_URI_METHOD_PARAMS+PARAMETER_TEMP_URI_SIGNATURE+Definitions.SEPARATOR_URI_QUERY_PARAM_VALUE+signature+Definitions.SEPARATOR_URI_QUERY_PARAMS+PARAMETER_TEMP_URI_EXPIRATION+Definitions.SEPARATOR_URI_QUERY_PARAM_VALUE+expires, new Date(expires*1000)); 194 } 195 196 /** 197 * 198 * @param objectName 199 * @param metadata 200 */ 201 public void update(String objectName, Map<String, String> metadata){ 202 getObjectApi().updateMetadata(objectName, metadata); //TODO check what this does if the objectName is invalid or non-existent 203 } 204}