/* * Copyright 2008-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kaleidofoundry.core.store; import static org.kaleidofoundry.core.env.model.EnvironmentConstants.DEFAULT_BASE_DIR_PROPERTY; import static org.kaleidofoundry.core.env.model.EnvironmentConstants.STATIC_ENV_PARAMETERS; import static org.kaleidofoundry.core.i18n.InternalBundleHelper.StoreMessageBundle; import static org.kaleidofoundry.core.store.AbstractFileStore.LOGGER; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.Set; import org.kaleidofoundry.core.context.AbstractProviderService; import org.kaleidofoundry.core.context.EmptyContextParameterException; import org.kaleidofoundry.core.context.ProviderException; import org.kaleidofoundry.core.context.RuntimeContext; import org.kaleidofoundry.core.io.FileHelper; import org.kaleidofoundry.core.lang.annotation.NotNull; import org.kaleidofoundry.core.plugin.PluginFactory; import org.kaleidofoundry.core.plugin.model.Plugin; import org.kaleidofoundry.core.util.Registry; import org.kaleidofoundry.core.util.StringHelper; /** * {@link FileStore} Provider * * @author jraduget */ public class FileStoreProvider extends AbstractProviderService<FileStore> { /** * files store internal registry */ static final Registry<String, FileStore> REGISTRY = new Registry<String, FileStore>(); // does default init Configurations have occurred private static boolean INIT_LOADED = false; // The default base directory (use to merge resource uri with ${basedir.default} variable) private static String DEFAULT_BASE_DIR; /** * @param basedir */ public static synchronized final void init(final String basedir) { if (!INIT_LOADED) { // base dir if defined String currentBaseDir = StringHelper.isEmpty(basedir) ? System.getProperty(DEFAULT_BASE_DIR_PROPERTY) : basedir; if (StringHelper.isEmpty(currentBaseDir)) { currentBaseDir = FileHelper.buildUnixAppPath(FileHelper.getCurrentPath()); } currentBaseDir = currentBaseDir.endsWith("/") ? currentBaseDir.substring(0, currentBaseDir.length() - 1) : currentBaseDir; DEFAULT_BASE_DIR = (currentBaseDir.startsWith("/") ? currentBaseDir.substring(1) : currentBaseDir); STATIC_ENV_PARAMETERS.put("basedir", DEFAULT_BASE_DIR); STATIC_ENV_PARAMETERS.put("default.basedir", DEFAULT_BASE_DIR); STATIC_ENV_PARAMETERS.put(DEFAULT_BASE_DIR_PROPERTY, DEFAULT_BASE_DIR); LOGGER.info(StoreMessageBundle.getMessage("store.basedir.info", "basedir", DEFAULT_BASE_DIR)); INIT_LOADED = true; } } /** * @param genericClass */ public FileStoreProvider(final Class<FileStore> genericClass) { super(genericClass); } /* * (non-Javadoc) * @see org.kaleidofoundry.core.context.AbstractProviderService#getRegistry() */ @Override protected Registry<String, FileStore> getRegistry() { return REGISTRY; } /* * (non-Javadoc) * @see org.kaleidofoundry.core.context.Provider#_provides(org.kaleidofoundry.core.context.RuntimeContext) */ @Override public FileStore _provides(@NotNull final RuntimeContext<FileStore> context) throws ProviderException { final String baseUri = context.getString(FileStoreContextBuilder.BaseUri); if (StringHelper.isEmpty(baseUri)) { throw new EmptyContextParameterException(FileStoreContextBuilder.BaseUri, context); } return provides(baseUri, context); } /** * @param baseUri * file store root path uri, which looks like (path is optional) : * <ul> * <li><code>http://host/</code> <b>or</b> <code>http://host/path</code></li> * <li><code>ftp://host/</code> <b>or</b> <code>ftp://host/path</code></li> * <li><code>classpath:/</code> <b>or</b> <code>classpath:/path</code></li> * <li><code>file:/</code> <b>or</b> <code>file:/path</code></li> * <li><code>webapp:/</code> <b>or</b> <code>webapp:/path</code></li> * <li><code>...</li> * </ul> * <b>uri schemes handled</b>: <code>http|https|ftp|file|classpath|webapp|...</code> * @return new file store instance * @throws ProviderException encapsulate class implementation constructor call error (like {@link NoSuchMethodException}, * {@link InstantiationException}, {@link IllegalAccessException}, {@link InvocationTargetException}) */ public FileStore provides(final String baseUri) throws ProviderException { FileStore fileStore = getRegistry().get(baseUri); if (fileStore == null) { fileStore = provides(baseUri, new FileStoreContextBuilder(baseUri).withBaseUri(baseUri).build()); } return fileStore; } /** * create a instance of {@link FileStore} analyzing a given {@link URI}<br/> * scheme of the uri is used to get the registered file store implementation. * * @param pBaseUri <br/> * file store root path uri, which looks like (path is optional) : * <ul> * <li><code>http://host/</code> <b>or</b> <code>http://host/path</code></li> * <li><code>ftp://host/</code> <b>or</b> <code>ftp://host/path</code></li> * <li><code>classpath:/</code> <b>or</b> <code>classpath:/path</code></li> * <li><code>file:/</code> <b>or</b> <code>file:/path</code></li> * <li><code>webapp:/</code> <b>or</b> <code>webapp:/path</code></li> * <li><code>...</li> * </ul> * <b>uri schemes handled</b>: <code>http|https|ftp|file|classpath|webapp|...</code> * @param pContext store runtime context * @return new file store instance, specific to the resource uri scheme * @throws ProviderException encapsulate class implementation constructor call error (like {@link NoSuchMethodException}, * {@link InstantiationException}, {@link IllegalAccessException}, {@link InvocationTargetException}) */ public FileStore provides(@NotNull final String pBaseUri, @NotNull final RuntimeContext<FileStore> pContext) throws ProviderException { final String mergedBaseUri = buildFullResourceURi(pBaseUri); final URI baseURI = createURI(mergedBaseUri); final FileStoreType fileStoreType = FileStoreTypeEnum.match(baseURI); if (fileStoreType != null) { final Set<Plugin<FileStore>> pluginImpls = PluginFactory.getImplementationRegistry().findByInterface(FileStore.class); // scan each @Declare file store implementation, to get one which handle the uri scheme for (final Plugin<FileStore> pi : pluginImpls) { final Class<? extends FileStore> impl = pi.getAnnotatedClass(); try { final Constructor<? extends FileStore> constructor = impl.getConstructor(String.class, pContext.getClass()); final FileStore fileStore = constructor.newInstance(mergedBaseUri, pContext); try { if (fileStore.isUriManageable(mergedBaseUri)) { if (pContext.getName() != null) { getRegistry().put(pContext.getName(), fileStore); } return fileStore; } } catch (final IllegalArgumentException iae) { } // unregister wrong file store if (pContext.getName() != null) { getRegistry().remove(pContext.getName()); } } catch (final NoSuchMethodException e) { throw new ProviderException("context.provider.error.NoSuchConstructorException", impl.getName(), "RuntimeContext<FileStore> context"); } catch (final InstantiationException e) { throw new ProviderException("context.provider.error.InstantiationException", impl.getName(), e.getMessage()); } catch (final IllegalAccessException e) { throw new ProviderException("context.provider.error.IllegalAccessException", impl.getName(), "RuntimeContext<FileStore> context"); } catch (final InvocationTargetException e) { if (e.getCause() instanceof ResourceException) { throw new ProviderException(e.getCause()); } else { throw new ProviderException("context.provider.error.InvocationTargetException", e.getCause(), impl.getName(), "RuntimeContext<FileStore> context", e.getCause().getClass().getName(), e.getCause().getMessage()); } } } throw new IllegalArgumentException(StoreMessageBundle.getMessage("store.uri.notmanaged.illegal", mergedBaseUri.toString())); } throw new IllegalArgumentException(StoreMessageBundle.getMessage("store.uri.notmanaged", mergedBaseUri.toString(), baseURI.getScheme())); } // use to resolve uri although pUri contains only the uri scheme like ftp|http|classpath... private static URI createURI(@NotNull final String pUri) { // replace all ${param} by "", to avoid URISyntaxException String uri = pUri.replaceAll("\\$\\{.+\\}", ""); if (!uri.contains(":") && !uri.contains("/")) { return URI.create(uri + ":/"); } else { return URI.create(uri); } } /** * A {@link FileStore} resource uri, can contains some variables like ${application.basedir} ...<br.> * This class merge this variable if needed, in order to build a full valid {@link URI} for the filestore * * @param resourcePath the resource uri to merge with the system variables * @return the merged resource uri */ public static String buildFullResourceURi(String resourcePath) { // merge variables in the resource path if needed if (resourcePath.contains("${")) { resourcePath = StringHelper.resolveExpression(resourcePath, STATIC_ENV_PARAMETERS); } // only needed by SystemFileStore if (resourcePath.contains("file:/..")) { String parentPath = FileHelper.buildUnixAppPath(FileHelper.getParentPath()); resourcePath = StringHelper.replaceAll(resourcePath, "file:/..", "file:/" + (parentPath.startsWith("/") ? parentPath.substring(1) : parentPath)); } else if (resourcePath.startsWith("file:/.")) { String currentPath = FileHelper.buildUnixAppPath(FileHelper.getCurrentPath()); resourcePath = StringHelper.replaceAll(resourcePath, "file:/.", "file:/" + (currentPath.startsWith("/") ? currentPath.substring(1) : currentPath)); } return resourcePath; } }