All Downloads are FREE. Search and download functionalities are using the official Maven repository.

grails.core.DefaultGrailsApplication Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2004-2005 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 grails.core;

import grails.config.Config;
import grails.core.events.ArtefactAdditionEvent;
import grails.util.GrailsNameUtils;
import grails.util.GrailsUtil;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaMethod;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.runtime.m12n.ExtensionModuleScanner;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
import org.grails.config.PropertySourcesConfig;
import org.grails.core.AbstractGrailsApplication;
import org.grails.core.artefact.DomainClassArtefactHandler;
import org.grails.core.exceptions.GrailsConfigurationException;
import org.grails.core.io.support.GrailsFactoriesLoader;
import org.grails.io.support.GrailsResourceUtils;
import org.grails.spring.beans.GrailsApplicationAwareBeanPostProcessor;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Default implementation of the GrailsApplication interface that manages application loading,
 * state, and artefact instances.
 * 

* Upon loading this GrailsApplication will inspect each class using its registered ArtefactHandler instances. Each * ArtefactHandler provides knowledge about the conventions used to establish its artefact type. For example * controllers use the ControllerArtefactHandler to establish this knowledge. *

* New ArtefactHandler instances can be registered with the GrailsApplication thus allowing application extensibility. * * @author Marc Palmer * @author Steven Devijver * @author Graeme Rocher * * @see grails.plugins.GrailsPluginManager * @see grails.plugins.DefaultGrailsPluginManager * @see grails.core.ArtefactHandler * @see grails.core.ArtefactInfo * @since 0.1 */ public class DefaultGrailsApplication extends AbstractGrailsApplication implements BeanClassLoaderAware { protected static final Pattern GETCLASSESPROP_PATTERN = Pattern.compile("(\\w+)(Classes)"); protected static final Pattern GETCLASSESMETH_PATTERN = Pattern.compile("(get)(\\w+)(Classes)"); protected static final Pattern ISCLASS_PATTERN = Pattern.compile("(is)(\\w+)(Class)"); protected static final Pattern GETCLASS_PATTERN = Pattern.compile("(get)(\\w+)Class"); protected Class[] allClasses = new Class[0]; protected static Log log = LogFactory.getLog(DefaultGrailsApplication.class); protected Set> loadedClasses = new LinkedHashSet>(); protected ArtefactHandler[] artefactHandlers; protected Map artefactHandlersByName = new HashMap(); protected List> allArtefactClasses = new ArrayList>(); protected Map artefactInfo = new HashMap(); protected Class[] allArtefactClassesArray; protected Resource[] resources; protected boolean initialised = false; /** * Creates a new empty Grails application. */ public DefaultGrailsApplication() { this(new GroovyClassLoader()); } public DefaultGrailsApplication(ClassLoader classLoader) { super(); this.classLoader = classLoader; } /** * Construct an application for the given classes * * @param classes The classes */ public DefaultGrailsApplication(final Class...classes) { this(classes, new GroovyClassLoader(Thread.currentThread().getContextClassLoader())); } /** * Creates a new GrailsApplication instance using the given classes and GroovyClassLoader. * * @param classes The classes that make up the GrailsApplication * @param classLoader The GroovyClassLoader to use */ public DefaultGrailsApplication(final Class[] classes, ClassLoader classLoader) { super(); Assert.notNull(classes, "Constructor argument 'classes' cannot be null"); loadedClasses.addAll(Arrays.asList(classes)); allClasses = classes; this.classLoader = classLoader; } /** * Loads a GrailsApplication using the given ResourceLocator instance which will search for appropriate class names * */ public DefaultGrailsApplication(Resource[] resources) { this(); for (Resource resource : resources) { Class aClass; try { aClass = classLoader.loadClass(GrailsResourceUtils.getClassName(resource.getFile().getAbsolutePath())); } catch (ClassNotFoundException e) { throw new GrailsConfigurationException("Class not found loading Grails application: " + e.getMessage(), e); } catch (IOException e) { throw new GrailsConfigurationException("Class not found loading Grails application: " + e.getMessage(), e); } loadedClasses.add(aClass); } } /** * Loads a GrailsApplication using the given ResourceLocator instance which will search for appropriate class names * */ public DefaultGrailsApplication(org.grails.io.support.Resource[] resources) { this(); for (org.grails.io.support.Resource resource : resources) { Class aClass; try { aClass = classLoader.loadClass(GrailsResourceUtils.getClassName(resource.getFile().getAbsolutePath())); } catch (ClassNotFoundException e) { throw new GrailsConfigurationException("Class not found loading Grails application: " + e.getMessage(), e); } catch (IOException e) { throw new GrailsConfigurationException("Class not found loading Grails application: " + e.getMessage(), e); } loadedClasses.add(aClass); } } /** * Initialises the default set of ArtefactHandler instances. * * @see grails.core.ArtefactHandler */ @SuppressWarnings( "deprecation" ) protected void initArtefactHandlers() { List legacyArtefactHandlers = GrailsFactoriesLoader.loadFactories(org.codehaus.groovy.grails.commons.ArtefactHandler.class, getClassLoader()); for (org.codehaus.groovy.grails.commons.ArtefactHandler artefactHandler : legacyArtefactHandlers) { registerArtefactHandler(artefactHandler); } List additionalArtefactHandlers = GrailsFactoriesLoader.loadFactories(ArtefactHandler.class, getClassLoader()); for (ArtefactHandler artefactHandler : additionalArtefactHandlers) { registerArtefactHandler(artefactHandler); } updateArtefactHandlers(); } private void updateArtefactHandlers() { // Cache the list as an array artefactHandlers = artefactHandlersByName.values().toArray( new org.codehaus.groovy.grails.commons.ArtefactHandler[artefactHandlersByName.size()]); } /** * Returns all the classes identified as artefacts by ArtefactHandler instances. * * @return An array of classes */ public Class[] getAllArtefacts() { return allArtefactClassesArray; } protected Class[] populateAllClasses() { allClasses = loadedClasses.toArray(new Class[loadedClasses.size()]); return allClasses; } /** * Configures the loaded classes within the GrailsApplication instance using the * registered ArtefactHandler instances. * * @param classes The classes to configure */ protected void configureLoadedClasses(Class[] classes) { initArtefactHandlers(); artefactInfo.clear(); allArtefactClasses.clear(); allArtefactClassesArray = null; allClasses = classes; // first load the domain classes log.debug("Going to inspect artefact classes."); MetaClassRegistry metaClassRegistry = GroovySystem.getMetaClassRegistry(); for (final Class theClass : classes) { log.debug("Inspecting [" + theClass.getName() + "]"); // start fresh metaClassRegistry.removeMetaClass(theClass); if (allArtefactClasses.contains(theClass)) { continue; } // check what kind of artefact it is and add to corrent data structure for (ArtefactHandler artefactHandler : artefactHandlers) { if (artefactHandler.isArtefact(theClass)) { log.debug("Adding artefact " + theClass + " of kind " + artefactHandler.getType()); GrailsClass gclass = addArtefact(artefactHandler.getType(), theClass); // Also maintain set of all artefacts (!= all classes loaded) allArtefactClasses.add(theClass); // Update per-artefact cache DefaultArtefactInfo info = getArtefactInfo(artefactHandler.getType(), true); info.addGrailsClass(gclass); break; } } } refreshArtefactGrailsClassCaches(); allArtefactClassesArray = allArtefactClasses.toArray(new Class[allArtefactClasses.size()]); // Tell all artefact handlers to init now we've worked out which classes are which artefacts for (ArtefactHandler artefactHandler : artefactHandlers) { initializeArtefacts(artefactHandler); } } /** * Tell all our artefact info objects to update their internal state after we've added a bunch of classes. */ protected void refreshArtefactGrailsClassCaches() { for (Object o : artefactInfo.values()) { ((DefaultArtefactInfo)o).updateComplete(); } } protected void addToLoaded(Class clazz) { loadedClasses.add(clazz); populateAllClasses(); } public Config getConfig() { if (config == null) { if(parentContext != null) { org.springframework.core.env.Environment environment = parentContext.getEnvironment(); if(environment instanceof ConfigurableEnvironment) { MutablePropertySources propertySources = ((ConfigurableEnvironment) environment).getPropertySources(); this.config = new PropertySourcesConfig(propertySources); } } else { this.config = new PropertySourcesConfig(); } setConfig(this.config); } return config; } /** * Retrieves the number of artefacts registered for the given artefactType as defined by the ArtefactHandler. * * @param artefactType The type of the artefact as defined by the ArtefactHandler * @return The number of registered artefacts */ protected int getArtefactCount(String artefactType) { org.codehaus.groovy.grails.commons.ArtefactInfo info = getArtefactInfo(artefactType); return info == null ? 0 : info.getClasses().length; } /** * Retrieves all classes loaded by the GrailsApplication. * * @return All classes loaded by the GrailsApplication */ public Class[] getAllClasses() { return allClasses; } /** * Retrieves a class from the GrailsApplication for the given name. * * @param className The class name * @return Either the Class instance or null if it doesn't exist */ public Class getClassForName(String className) { if (!StringUtils.hasText(className)) { return null; } for (Class c : allClasses) { if (c.getName().equals(className)) { return c; } } return null; } /** * Refreshes constraints defined by the DomainClassArtefactHandler. * * TODO: Move this out of GrailsApplication */ public void refreshConstraints() { ArtefactInfo info = getArtefactInfo(DomainClassArtefactHandler.TYPE, true); GrailsClass[] domainClasses = info.getGrailsClasses(); for (GrailsClass domainClass : domainClasses) { ((GrailsDomainClass) domainClass).refreshConstraints(); } } /** * Refreshes this GrailsApplication, rebuilding all of the artefact definitions as * defined by the registered ArtefactHandler instances. */ public void refresh() { if (classLoader instanceof GroovyClassLoader) { configureLoadedClasses(((GroovyClassLoader)classLoader).getLoadedClasses()); } } public void rebuild() { initialised = false; loadedClasses.clear(); initArtefactHandlers(); if (GrailsUtil.isDevelopmentEnv()) { initialise(); } else { throw new IllegalStateException("Cannot rebuild GrailsApplication when not in development mode!"); } } /** * Retrieves the Spring Resource that was used to load the given Class. * * @param theClazz The class * @return Either a Spring Resource or null if no Resource was found for the given class */ public Resource getResourceForClass(@SuppressWarnings("rawtypes") Class theClazz) { // TODO fix return null; } /** * Returns true if the given class is an artefact identified by one of the registered * ArtefactHandler instances. Uses class name equality to handle class reloading * * @param theClazz The class to check * @return true if it is an artefact */ public boolean isArtefact(@SuppressWarnings("rawtypes") Class theClazz) { String className = theClazz.getName(); for (Class artefactClass : allArtefactClasses) { if (className.equals(artefactClass.getName())) { return true; } } return false; } /** * Returns true if the specified class is of the given artefact type as defined by the ArtefactHandler. * * @param artefactType The type of the artefact * @param theClazz The class * @return true if it is of the specified artefactType * @see grails.core.ArtefactHandler */ public boolean isArtefactOfType(String artefactType, @SuppressWarnings("rawtypes") Class theClazz) { ArtefactHandler handler = artefactHandlersByName.get(artefactType); if (handler == null) { throw new GrailsConfigurationException( "Unable to locate arefact handler for specified type: " + artefactType); } return handler.isArtefact(theClazz); } /** * Returns true if the specified class name is of the given artefact type as defined by the ArtefactHandler. * * @param artefactType The type of the artefact * @param className The class name * @return true if it is of the specified artefactType * @see grails.core.ArtefactHandler */ public boolean isArtefactOfType(String artefactType, String className) { return getArtefact(artefactType, className) != null; } /** * Retrieves an artefact for the given type and name. * * @param artefactType The artefact type as defined by a registered ArtefactHandler * @param name The name of the class * @return A GrailsClass instance or null if none could be found for the given artefactType and name */ public GrailsClass getArtefact(String artefactType, String name) { ArtefactInfo info = getArtefactInfo(artefactType); return info == null ? null : info.getGrailsClass(name); } public ArtefactHandler getArtefactType(@SuppressWarnings("rawtypes") Class theClass) { for (ArtefactHandler artefactHandler : artefactHandlers) { if (artefactHandler.isArtefact(theClass)) { return artefactHandler; } } return null; } protected GrailsClass getFirstArtefact(String artefactType) { ArtefactInfo info = getArtefactInfo(artefactType); // This will throw AIOB if we have none return info == null ? null : info.getGrailsClasses()[0]; } /** * Returns all of the GrailsClass instances for the given artefactType as defined by the ArtefactHandler * * @param artefactType The type of the artefact defined by the ArtefactHandler * @return An array of classes for the given artefact */ public GrailsClass[] getArtefacts(String artefactType) { return getArtefactInfo(artefactType, true).getGrailsClasses(); } /** * Adds an artefact of the given type for the given Class. * * @param artefactType The type of the artefact as defined by a ArtefactHandler instance * @param artefactClass A Class instance that matches the type defined by the ArtefactHandler * @return The GrailsClass if successful or null if it couldn't be added * @throws GrailsConfigurationException If the specified Class is not the same as the type defined by the ArtefactHandler * @see grails.core.ArtefactHandler */ public GrailsClass addArtefact(String artefactType, @SuppressWarnings("rawtypes") Class artefactClass) { return addArtefact(artefactType, artefactClass, false); } /** * Adds an artefact of the given type for the given GrailsClass. * * @param artefactType The type of the artefact as defined by a ArtefactHandler instance * @param artefactGrailsClass A GrailsClass instance that matches the type defined by the ArtefactHandler * @return The GrailsClass if successful or null if it couldn't be added * @throws GrailsConfigurationException If the specified GrailsClass is not the same as the type defined by the ArtefactHandler * @see grails.core.ArtefactHandler */ public GrailsClass addArtefact(String artefactType, GrailsClass artefactGrailsClass) { ArtefactHandler handler = artefactHandlersByName.get(artefactType); if (handler.isArtefactGrailsClass(artefactGrailsClass)) { // Store the GrailsClass in cache DefaultArtefactInfo info = getArtefactInfo(artefactType, true); info.addGrailsClass(artefactGrailsClass); info.updateComplete(); initializeArtefacts(artefactType); return artefactGrailsClass; } throw new GrailsConfigurationException("Cannot add " + artefactType + " class [" + artefactGrailsClass + "]. It is not a " + artefactType + "!"); } /** * Registers a new ArtefactHandler that is responsible for identifying and managing a * particular artefact type that is defined by some convention. * * @param handler The ArtefactHandler to regster */ public void registerArtefactHandler(ArtefactHandler handler) { GrailsApplicationAwareBeanPostProcessor.processAwareInterfaces(this, handler); artefactHandlersByName.put(handler.getType(), handler); updateArtefactHandlers(); } public boolean hasArtefactHandler(String type) { return artefactHandlersByName.containsKey(type); } public ArtefactHandler[] getArtefactHandlers() { return artefactHandlers; } public ArtefactHandler getArtefactHandler(String type) { return artefactHandlersByName.get(type); } /** * Re-initialize the artefacts of the specified type. This gives handlers a chance to update caches etc. * * @param artefactType The type of artefact to init */ protected void initializeArtefacts(String artefactType) { initializeArtefacts(artefactHandlersByName.get(artefactType)); } /** * Clears the application returning it to an empty state. Very dangerous method, use with caution. */ public void clear() { artefactHandlersByName.clear(); updateArtefactHandlers(); artefactInfo.clear(); initialise(); } /** * Re-initialize the artefacts of the specified type. This gives handlers a chance to update caches etc. * * @param handler The handler to register */ protected void initializeArtefacts(ArtefactHandler handler) { if (handler == null) { return; } org.codehaus.groovy.grails.commons.ArtefactInfo info = getArtefactInfo(handler.getType()); // Only init those that have data if (info != null) { //System.out.println("Initialising artefacts of kind " + handler.getType() + " with registered artefacts" + info.getGrailsClassesByName()); handler.initialize(info); } } /** * Get or create the cache of classes for the specified artefact type. * * @param artefactType The name of an artefact type * @param create Set to true if you want non-existent caches to be created * @return The cache of classes for the type, or null if no cache exists and create is false */ protected DefaultArtefactInfo getArtefactInfo(String artefactType, boolean create) { DefaultArtefactInfo cache = (DefaultArtefactInfo) artefactInfo.get(artefactType); if (cache == null && create) { cache = new DefaultArtefactInfo(); artefactInfo.put(artefactType, cache); cache.updateComplete(); } return cache; } /** * Get the cache of classes for the specified artefact type. * * @param artefactType The name of an artefact type * @return The cache of classes for the type, or null if no cache exists */ public org.codehaus.groovy.grails.commons.ArtefactInfo getArtefactInfo(String artefactType) { return getArtefactInfo(artefactType, false); } /** *

Overrides method invocation to return dynamic artefact methods.

*

We will support getXXXXClasses() and isXXXXClass(class)

* * @param methodName The name of the method * @param args The arguments to the method * @return The return value of the method * TODO Need to add matches for addClass(java.lang.Class) and addClass(GrailsClass) */ @Override public Object invokeMethod(String methodName, Object args) { Object[] argsv = (Object[]) args; Matcher match = GETCLASS_PATTERN.matcher(methodName); // look for getXXXXClass(y) match.find(); if (match.matches()) { if (argsv.length > 0) { if (argsv[0] instanceof CharSequence) argsv[0] = argsv[0].toString(); if ((argsv.length != 1) || !(argsv[0] instanceof String)) { throw new IllegalArgumentException( "Dynamic method getClass(artefactName) requires a single String parameter"); } return getArtefact(match.group(2), argsv[0].toString()); } // It's a no-param getter return super.invokeMethod(methodName, args); } // look for isXXXXClass(y) match = ISCLASS_PATTERN.matcher(methodName); // find match match.find(); if (match.matches()) { if ((argsv.length != 1) || !(argsv[0] instanceof Class)) { throw new IllegalArgumentException( "Dynamic method isClass(artefactClass) requires a single Class parameter"); } return isArtefactOfType(match.group(2), (Class) argsv[0]); } // look for getXXXXClasses match = GETCLASSESMETH_PATTERN.matcher(methodName); // find match match.find(); if (match.matches()) { String artefactName = GrailsNameUtils.getClassNameRepresentation(match.group(2)); if (artefactHandlersByName.containsKey(artefactName)) { return getArtefacts(match.group(2)); } throw new IllegalArgumentException("Dynamic method getClasses() called for " + "unrecognized artefact: " + match.group(2)); } return super.invokeMethod(methodName, args); } /** * Override property access and hit on xxxxClasses to return class arrays of artefacts. * * @param propertyName The name of the property, if it ends in *Classes then match and invoke internal ArtefactHandler * @return All the artifacts or delegate to super.getProperty */ @Override public Object getProperty(String propertyName) { // look for getXXXXClasses final Matcher match = GETCLASSESPROP_PATTERN.matcher(propertyName); // find match match.find(); if (match.matches()) { String artefactName = GrailsNameUtils.getClassNameRepresentation(match.group(1)); if (artefactHandlersByName.containsKey(artefactName)) { return getArtefacts(artefactName); } } return super.getProperty(propertyName); } public void initialise() { // get all the classes that were loaded if (log.isDebugEnabled()) { log.debug("loaded classes: [" + loadedClasses + "]"); } Class[] classes = populateAllClasses(); configureLoadedClasses(classes); initialiseGroovyExtensionModules(); initialised = true; } private static boolean extensionMethodsInitialized = false; protected static void initialiseGroovyExtensionModules() { if(extensionMethodsInitialized) return; extensionMethodsInitialized = true; Map> map = new HashMap>(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Enumeration resources = classLoader.getResources(ExtensionModuleScanner.MODULE_META_INF_FILE); while (resources.hasMoreElements()) { URL url = resources.nextElement(); if (url.getPath().contains("groovy-all")) { // already registered continue; } Properties properties = new Properties(); InputStream inStream = null; try { inStream = url.openStream(); properties.load(inStream); ((MetaClassRegistryImpl)GroovySystem.getMetaClassRegistry()).registerExtensionModuleFromProperties(properties, classLoader, map); } catch (IOException e) { throw new GroovyRuntimeException("Unable to load module META-INF descriptor", e); } finally { if(inStream != null) { inStream.close(); } } } } catch (IOException ignored) {} for (Map.Entry> moduleMethods : map.entrySet()) { CachedClass cls = moduleMethods.getKey(); cls.addNewMopMethods( moduleMethods.getValue() ); } } // This is next call is equiv to getControllerByURI / getTagLibForTagName public GrailsClass getArtefactForFeature(String artefactType, Object featureID) { return artefactHandlersByName.get(artefactType).getArtefactForFeature(featureID); } public boolean isInitialised() { return initialised; } public GrailsClass getArtefactByLogicalPropertyName(String type, String logicalName) { ArtefactInfo info = getArtefactInfo(type); return info == null ? null : info.getGrailsClassByLogicalPropertyName(logicalName); } public void addArtefact(@SuppressWarnings("rawtypes") Class artefact) { for (ArtefactHandler artefactHandler : artefactHandlers) { if (artefactHandler.isArtefact(artefact)) { addArtefact(artefactHandler.getType(), artefact); } } } public void setBeanClassLoader(ClassLoader classLoader) { // do nothing } public void addOverridableArtefact(@SuppressWarnings("rawtypes") Class artefact) { for (ArtefactHandler artefactHandler : artefactHandlers) { if (artefactHandler.isArtefact(artefact)) { addOverridableArtefact(artefactHandler.getType(), artefact); } } } /** * Adds an artefact of the given type for the given Class. * * @param artefactType The type of the artefact as defined by a ArtefactHandler instance * @param artefactClass A Class instance that matches the type defined by the ArtefactHandler * @return The GrailsClass if successful or null if it couldn't be added * @throws GrailsConfigurationException If the specified Class is not the same as the type defined by the ArtefactHandler * @see grails.core.ArtefactHandler */ public GrailsClass addOverridableArtefact(String artefactType, @SuppressWarnings("rawtypes") Class artefactClass) { return addArtefact(artefactType, artefactClass, true); } protected GrailsClass addArtefact(String artefactType, Class artefactClass, boolean overrideable) { ArtefactHandler handler = artefactHandlersByName.get(artefactType); if (handler != null && handler.isArtefact(artefactClass)) { GrailsClass artefactGrailsClass = handler.newArtefactClass(artefactClass); artefactGrailsClass.setGrailsApplication(this); // Store the GrailsClass in cache DefaultArtefactInfo info = getArtefactInfo(artefactType, true); if (overrideable) { info.addOverridableGrailsClass(artefactGrailsClass); } else { info.addGrailsClass(artefactGrailsClass); } info.updateComplete(); addToLoaded(artefactClass); if (isInitialised()) { initializeArtefacts(artefactType); ApplicationContext context = getMainContext(); if(context instanceof ConfigurableApplicationContext && contextInitialized && ((ConfigurableApplicationContext) context).isActive()) { context.publishEvent(new ArtefactAdditionEvent(artefactGrailsClass)); } } return artefactGrailsClass; } throw new GrailsConfigurationException("Cannot add " + artefactType + " class [" + artefactClass + "]. It is not a " + artefactType + "!"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy