प्रवेश गुण वसंत के साथ प्रोग्राम फ़ाइल?


137

गुणों की फ़ाइल से गुणों के साथ स्प्रिंग बीन्स को इंजेक्ट करने के लिए हम नीचे दिए गए कोड का उपयोग करते हैं।

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:/my.properties"/>
</bean>

<bean id="blah" class="abc">
    <property name="path" value="${the.path}"/>
</bean>

क्या कोई ऐसा तरीका है जिससे हम प्रोग्राम को गुणों तक पहुंचा सकते हैं? मैं निर्भरता इंजेक्शन के बिना कुछ कोड करने की कोशिश कर रहा हूं। तो मैं बस कुछ कोड इस तरह करना चाहूंगा:

PropertyPlaceholderConfigurer props = new PropertyPlaceholderConfigurer();
props.load("classpath:/my.properties");
props.get("path");

वसंत में गुण फ़ाइल तक पहुँचने का एक पूरा उदाहरण निम्नलिखित लिंक पर है: bharatonjava.wordpress.com/2012/08/24/…

जवाबों:


171

कैसे के बारे में PropertiesLoaderUtils ?

Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);

5
यहाँ एक सवाल है, यह मेरा से कैसे अलग है, और दो और वोट हैं और दूसरे पोस्ट किए हैं ...
Zoidberg

3
मुझे मारता है, मुझे वोट नहीं मिला :) मैं एक का उपयोग नहीं करूंगा PropertyPlaceholderConfigurer, हालांकि यह कार्य के लिए ओवरकिल है।
स्केफमैन

5
मैं जितना संभव हो सके उसके करीब जाने की कोशिश कर रहा था, मुझे पर्याप्त विवरण प्रदान नहीं करने के लिए कई बार उतारा गया। किसी भी तरह से, आपके उत्तर वोट के लायक हैं, क्योंकि यह सही है, मुझे लगता है कि मैं सिर्फ ईर्ष्या कर रहा हूं मुझे 2 वोट भी नहीं मिले, LOL।
Zoidberg

1
यदि फ़ाइल को बाहरी निर्देशिका में रखा गया है, तो हमें पथ में क्या देना चाहिए?
प्रांजन

52

यदि आप सभी को कोड से प्लेसहोल्डर मान एक्सेस करना चाहते हैं, तो @Valueएनोटेशन है:

@Value("${settings.some.property}")
String someValue;

SPEL से प्लेसहोल्डर्स तक पहुँचने के लिए इस सिंटैक्स का उपयोग करें:

#('${settings.some.property}')

SPEL को बंद करने वाले दृश्यों के लिए कॉन्फ़िगरेशन को उजागर करने के लिए, कोई भी इस चाल का उपयोग कर सकता है:

package com.my.app;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class PropertyPlaceholderExposer implements Map<String, String>, BeanFactoryAware {  
    ConfigurableBeanFactory beanFactory; 

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }

    protected String resolveProperty(String name) {
        String rv = beanFactory.resolveEmbeddedValue("${" + name + "}");

        return rv;
    }

    @Override
    public String get(Object key) {
        return resolveProperty(key.toString());
    }

    @Override
    public boolean containsKey(Object key) {
        try {
            resolveProperty(key.toString());
            return true;
        }
        catch(Exception e) {
            return false;
        }
    }

    @Override public boolean isEmpty() { return false; }
    @Override public Set<String> keySet() { throw new UnsupportedOperationException(); }
    @Override public Set<java.util.Map.Entry<String, String>> entrySet() { throw new UnsupportedOperationException(); }
    @Override public Collection<String> values() { throw new UnsupportedOperationException(); }
    @Override public int size() { throw new UnsupportedOperationException(); }
    @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); }
    @Override public void clear() { throw new UnsupportedOperationException(); }
    @Override public String put(String key, String value) { throw new UnsupportedOperationException(); }
    @Override public String remove(Object key) { throw new UnsupportedOperationException(); }
    @Override public void putAll(Map<? extends String, ? extends String> t) { throw new UnsupportedOperationException(); }
}

और फिर एक्सपोज़र का उपयोग करके गुणों को एक दृश्य में उजागर करें:

<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
    <property name="attributesMap">
        <map>
            <entry key="config">
                <bean class="com.my.app.PropertyPlaceholderExposer" />
            </entry>
        </map>
    </property>
</bean>

फिर, इस तरह से उजागर गुणों का उपयोग करें:

${config['settings.some.property']}

इस समाधान का लाभ है कि आप मानक प्लेसहोल्डर कार्यान्वयन को संदर्भ के आधार पर भरोसा कर सकते हैं: संपत्ति-प्लेसहोल्डर टैग।

अब अंतिम नोट के रूप में, यदि आपको वास्तव में सभी प्लेसहोल्डर संपत्तियों और उनके मूल्यों पर कब्जा करने की आवश्यकता है, तो आपको स्ट्रिंगरवैल्यूएंडोल्वर के माध्यम से उन्हें पाइप करना होगा ताकि यह सुनिश्चित हो सके कि प्लेसहोल्डर संपत्ति के मूल्यों के अंदर काम करते हैं। निम्न कोड ऐसा करेगा।

package com.my.app;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.StringValueResolver;

public class AppConfig extends PropertyPlaceholderConfigurer implements Map<String, String> {

    Map<String, String> props = new HashMap<String, String>();

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
            throws BeansException {

        this.props.clear();
        for (Entry<Object, Object> e: props.entrySet())
            this.props.put(e.getKey().toString(), e.getValue().toString());

        super.processProperties(beanFactory, props);
    }

    @Override
    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            StringValueResolver valueResolver) {

        super.doProcessProperties(beanFactoryToProcess, valueResolver);

        for(Entry<String, String> e: props.entrySet())
            e.setValue(valueResolver.resolveStringValue(e.getValue()));
    }

    // Implement map interface to access stored properties
    @Override public Set<String> keySet() { return props.keySet(); }
    @Override public Set<java.util.Map.Entry<String, String>> entrySet() { return props.entrySet(); }
    @Override public Collection<String> values() { return props.values(); }
    @Override public int size() { return props.size(); }
    @Override public boolean isEmpty() { return props.isEmpty(); }
    @Override public boolean containsValue(Object value) { return props.containsValue(value); }
    @Override public boolean containsKey(Object key) { return props.containsKey(key); }
    @Override public String get(Object key) { return props.get(key); }
    @Override public void clear() { throw new UnsupportedOperationException(); }
    @Override public String put(String key, String value) { throw new UnsupportedOperationException(); }
    @Override public String remove(Object key) { throw new UnsupportedOperationException(); }
    @Override public void putAll(Map<? extends String, ? extends String> t) { throw new UnsupportedOperationException(); }
}

इस पूर्ण जवाब के लिए Thnx! वहाँ अंतिम क्षेत्रों के साथ ऐसा करने का एक तरीका है?
वार्ड

2
@WardC आप एक अंतिम क्षेत्र में इंजेक्ट नहीं कर सकते। हालाँकि आप एक कंस्ट्रक्टर तर्क में इंजेक्ट कर सकते हैं और कन्स्ट्रक्टर के अंदर अंतिम फ़ील्ड मान सेट कर सकते हैं। देखें stackoverflow.com/questions/2306078/… और stackoverflow.com/questions/4203302/…
anttix

50

क्रेडिट : गुण फ़ाइल में फिर से पढ़ने के बिना वसंत में गुणों के लिए प्रोग्रामेटिक पहुंच

मैंने वसंत में पहले से ही लोड किए गए समान गुणों को पुनः लोड किए बिना प्रोग्रामों को वसंत में एक्सेस करने का एक अच्छा कार्यान्वयन पाया है। [इसके अलावा, स्रोत में संपत्ति फ़ाइल स्थान को हार्डकोड करना आवश्यक नहीं है]

इन परिवर्तनों के साथ, कोड क्लीनर और अधिक रखरखाव योग्य लगता है।

अवधारणा बहुत सरल है। बस स्प्रिंग डिफॉल्ट प्रॉपर्टी प्लेसहोल्डर (प्रॉपर्टीहोल्डरकॉन्फिगर) का विस्तार करें और स्थानीय चर में लोड होने वाले गुणों को कैप्चर करें

public class SpringPropertiesUtil extends PropertyPlaceholderConfigurer {

    private static Map<String, String> propertiesMap;
    // Default as in PropertyPlaceholderConfigurer
    private int springSystemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;

    @Override
    public void setSystemPropertiesMode(int systemPropertiesMode) {
        super.setSystemPropertiesMode(systemPropertiesMode);
        springSystemPropertiesMode = systemPropertiesMode;
    }

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException {
        super.processProperties(beanFactory, props);

        propertiesMap = new HashMap<String, String>();
        for (Object key : props.keySet()) {
            String keyStr = key.toString();
            String valueStr = resolvePlaceholder(keyStr, props, springSystemPropertiesMode);
            propertiesMap.put(keyStr, valueStr);
        }
    }

    public static String getProperty(String name) {
        return propertiesMap.get(name).toString();
    }

}

उपयोग उदाहरण

SpringPropertiesUtil.getProperty("myProperty")

वसंत विन्यास बदल जाता है

<bean id="placeholderConfigMM" class="SpringPropertiesUtil">
    <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
    <property name="locations">
    <list>
        <value>classpath:myproperties.properties</value>
    </list>
    </property>
</bean>

आशा है कि यह आपकी समस्याओं को हल करने में मदद करता है


8
यह पूर्ण कार्यान्वयन नहीं है और सही तरीके से काम नहीं करेगा। PropertyPlaceholderConfigurer नेस्टेड प्लेसहोल्डर्स सहित सभी प्लेसहोल्डर प्रॉपर्टीज़ को बदलने के लिए एक प्रॉपर्टीहोल्डर हेल्पर का उपयोग करता है। कलिंग के कार्यान्वयन में यदि आपके पास myFile = $ {myFolder} /myFile.txt जैसा कुछ है, तो आपको "myFile" कुंजी का उपयोग करके मानचित्र से प्राप्त होने वाली शाब्दिक संपत्ति का मूल्य $ {myFolder} /myFile.txt होगा।

1
यह सही उपाय है। ब्रायन की चिंता को दूर करने के लिए। $ {MyFolder} एक सिस्टम प्रॉपर्टी होनी चाहिए और गुण फ़ाइल में नहीं होनी चाहिए। इसका समाधान टोमैट सिस्टम प्रॉपर्टी या रन प्रॉपर्टी को ग्रहण में सेट करके किया जा सकता है। तुम भी एक निर्माण संपत्ति के लिए सक्षम हो सकता है। यह समाधान यह मान रहा है कि थोड़ा और उसे संबोधित करना चाहिए, लेकिन एक ही समय में यह उत्तर मानक अभ्यास की रेखा के साथ-साथ वसंत और जावा गुणों को अलग-अलग एक स्थान पर लोड करने के लिए बहुत अधिक है। एक अन्य विकल्प फ़ाइल में myFile के साथ एक सामान्य गुण फ़ाइल लोड करना है और शेष प्राप्त करने के लिए इसका उपयोग करना है।
रोब

1
मैंने इस वर्कअराउंड को स्प्रिंग 3.1+ से 'नए' प्रॉपर्टीजप्लसहोल्डर कांउफ़रगिज़र पर लागू करने की कोशिश की, लेकिन मैंने पाया है कि विधि प्रक्रियाप्रोपरिपेट्टीज़ (कॉन्फिगरेबल लाइस्टेबल बीनफ़ेक्टिंग, प्रॉपर्टीज़ प्रॉप्स) अब पदावनत हो गई है और इसलिए अब 'प्रॉप्स' तर्क तक कोई पहुँच नहीं है। प्रॉपर्टीज के स्रोतों को देखते हुएप्लासकॉन्फ्रेंगीगर को संपत्तियों को उजागर करने का एक साफ तरीका नहीं मिल सकता है। किसी भी विचार यह करने के लिए? धन्यवाद!
जोर्ज पलासियो

48

मैंने यह किया है और यह काम किया है।

Properties props = PropertiesLoaderUtils.loadAllProperties("my.properties");
PropertyPlaceholderConfigurer props2 = new PropertyPlaceholderConfigurer();
props2.setProperties(props);

वह काम करना चाहिए।


25

आप या तो स्प्रिंग बर्तन का उपयोग कर सकते हैं, या PropertiesFactoryBean के माध्यम से गुणों को लोड कर सकते हैं।

<util:properties id="myProps" location="classpath:com/foo/myprops.properties"/>

या:

<bean id="myProps" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
  <property name="location" value="classpath:com/foo/myprops.properties"/>
</bean>

तो आप उन्हें अपने आवेदन में चुन सकते हैं:

@Resource(name = "myProps")
private Properties myProps;

और अतिरिक्त रूप से इन गुणों का उपयोग अपने विन्यास में करें:

<context:property-placeholder properties-ref="myProps"/>

यह डॉक्स में भी है: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#xsd-config-body-schemas-util-properties


10

नीचे की तरह एक क्लास बनाएं

    package com.tmghealth.common.util;

    import java.util.Properties;

    import org.springframework.beans.BeansException;

    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

    import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.context.annotation.PropertySource;

    import org.springframework.stereotype.Component;


    @Component
    @Configuration
    @PropertySource(value = { "classpath:/spring/server-urls.properties" })
    public class PropertiesReader extends PropertyPlaceholderConfigurer {

        @Override
        protected void processProperties(
                ConfigurableListableBeanFactory beanFactory, Properties props)
                throws BeansException {
            super.processProperties(beanFactory, props);

        }

    }

फिर आप जहां भी प्रॉपर्टी यूज करना चाहते हैं

    @Autowired
        private Environment environment;
    and getters and setters then access using 

    environment.getProperty(envName
                    + ".letter.fdi.letterdetails.restServiceUrl");

- गौण कक्षा में गेटर्स और सेटर लिखें

    public Environment getEnvironment() {
            return environment;
        }`enter code here`

        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }

1
अब तक का सबसे अच्छा जवाब, बस पर्यावरण को स्वावलंबी बनाना चाहिए।
sbochins

4

जैसा कि आप जानते हैं कि स्प्रिंग के नए संस्करण प्रॉपर्टीप्लेहोल्डरकॉन्फिगरर का उपयोग नहीं करते हैं और अब प्रॉपर्टीजपॉल्डहोल्डरकॉन्फिगरर नामक एक और बुरे सपने का उपयोग करते हैं। यदि आप कोड से हल की गई संपत्तियों को प्राप्त करने की कोशिश कर रहे हैं, और चाहते हैं कि स्प्रिंग टीम ने हमें लंबे समय पहले ऐसा करने का तरीका दिया, तो इस पोस्ट को वोट करें! ... क्योंकि आप इसे नए तरीके से करते हैं:

उपवर्ग संपत्तिसुविधाएँ

public class SpringPropertyExposer extends PropertySourcesPlaceholderConfigurer {

    private ConfigurableListableBeanFactory factory;

    /**
     * Save off the bean factory so we can use it later to resolve properties
     */
    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            final ConfigurablePropertyResolver propertyResolver) throws BeansException {
        super.processProperties(beanFactoryToProcess, propertyResolver);

        if (beanFactoryToProcess.hasEmbeddedValueResolver()) {
            logger.debug("Value resolver exists.");
            factory = beanFactoryToProcess;
        }
        else {
            logger.error("No existing embedded value resolver.");
        }
    }

    public String getProperty(String name) {
        Object propertyValue = factory.resolveEmbeddedValue(this.placeholderPrefix + name + this.placeholderSuffix);
        return propertyValue.toString();
    }
}

इसका उपयोग करने के लिए, अपने @Configuration में अपने उपवर्ग का उपयोग करना सुनिश्चित करें और बाद में उपयोग के लिए इसका संदर्भ सहेजें।

@Configuration
@ComponentScan
public class PropertiesConfig {

    public static SpringPropertyExposer commonEnvConfig;

    @Bean(name="commonConfig")
    public static PropertySourcesPlaceholderConfigurer commonConfig() throws IOException {
        commonEnvConfig = new SpringPropertyExposer(); //This is a subclass of the return type.
        PropertiesFactoryBean commonConfig = new PropertiesFactoryBean();
        commonConfig.setLocation(new ClassPathResource("META-INF/spring/config.properties"));
        try {
            commonConfig.afterPropertiesSet();
        }
        catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
        commonEnvConfig.setProperties(commonConfig.getObject());
        return commonEnvConfig;
    }
}

उपयोग:

Object value = PropertiesConfig.commonEnvConfig.getProperty("key.subkey");

2

यहाँ एक और नमूना है।

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
cfg.postProcessBeanFactory(factory);


2

यह किसी भी नेस्टेड गुणों को हल करेगा।

public class Environment extends PropertyPlaceholderConfigurer {

/**
 * Map that hold all the properties.
 */
private Map<String, String> propertiesMap; 

/**
 * Iterate through all the Property keys and build a Map, resolve all the nested values before building the map.
 */
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException {
    super.processProperties(beanFactory, props);

    propertiesMap = new HashMap<String, String>();
    for (Object key : props.keySet()) {
        String keyStr = key.toString();
        String valueStr = beanFactory.resolveEmbeddedValue(placeholderPrefix + keyStr.trim() + DEFAULT_PLACEHOLDER_SUFFIX);
        propertiesMap.put(keyStr, valueStr);
    }
} 

/**
 * This method gets the String value for a given String key for the property files.
 * 
 * @param name - Key for which the value needs to be retrieved.
 * @return Value
 */
public String getProperty(String name) {
    return propertiesMap.get(name).toString();
}

2

आप Environmentवर्ग के माध्यम से अपने गुण प्राप्त कर सकते हैं । प्रलेखन के रूप में खड़ा है:

गुण लगभग सभी अनुप्रयोगों में एक महत्वपूर्ण भूमिका निभाते हैं, और विभिन्न प्रकार के स्रोतों से उत्पन्न हो सकते हैं: गुण फाइलें, जेवीएम सिस्टम गुण, सिस्टम पर्यावरण चर, जेएनडीआई, सर्वलेट संदर्भ पैरामीटर, तदर्थ गुण ऑब्जेक्ट्स, मैप्स, और इसी तरह। गुणों के संबंध में पर्यावरण वस्तु की भूमिका उपयोगकर्ता को संपत्ति स्रोतों को कॉन्फ़िगर करने और उनसे संपत्तियों को हल करने के लिए एक सुविधाजनक सेवा इंटरफ़ेस प्रदान करना है।

एक envचर के रूप में पर्यावरण होने , बस कॉल:

env.resolvePlaceholders("${your-property:default-value}")

आप अपने 'कच्चे' गुणों को प्राप्त कर सकते हैं:

env.getProperty("your-property")

यह उन सभी संपत्तियों स्रोत के माध्यम से खोजेगा जिन्हें वसंत ने पंजीकृत किया है।

आप या तो के माध्यम से पर्यावरण प्राप्त कर सकते हैं:

  • ApplicationContext को कार्यान्वित करके इंजेक्ट ApplicationContextAwareकरें और फिर getEnvironment()संदर्भ पर कॉल करें
  • लागू करें EnvironmentAware

यह एक वर्ग के कार्यान्वयन के माध्यम से प्राप्त होता है क्योंकि गुणों को एप्लिकेशन स्टार्टअप के शुरुआती चरण में हल किया जाता है, क्योंकि वे बीन निर्माण के लिए आवश्यक हो सकते हैं।

प्रलेखन पर अधिक पढ़ें: वसंत पर्यावरण प्रलेखन


1

इस पोस्ट में यह भी पता लगाया गया है कि एक्सेस प्रॉपर्टीज: http://maciej-miklas.blogspot.de/2013/07/spring-31-programmatic-access-to.html

आप ऐसे स्प्रिंग बीन पर स्प्रिंग प्रॉपर्टी-प्लेसहोल्डर द्वारा लोड की गई संपत्तियों तक पहुँच सकते हैं:

@Named
public class PropertiesAccessor {

    private final AbstractBeanFactory beanFactory;

    private final Map<String,String> cache = new ConcurrentHashMap<>();

    @Inject
    protected PropertiesAccessor(AbstractBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public  String getProperty(String key) {
        if(cache.containsKey(key)){
            return cache.get(key);
        }

        String foundProp = null;
        try {
            foundProp = beanFactory.resolveEmbeddedValue("${" + key.trim() + "}");
            cache.put(key,foundProp);
        } catch (IllegalArgumentException ex) {
           // ok - property was not found
        }

        return foundProp;
    }
}

0
create .properties file in classpath of your project and add path configuration in xml`<context:property-placeholder location="classpath*:/*.properties" />`

सर्वलेट-संदर्भ में। xml उसके बाद आप सीधे हर जगह अपनी फ़ाइल का उपयोग कर सकते हैं


0

कृपया अपने एप्लिकेशन के वर्ग पथ से फ़ाइल लोड करने के लिए अपनी स्प्रिंग कॉन्फ़िगरेशन फ़ाइल में नीचे दिए गए कोड का उपयोग करें

 <context:property-placeholder
    ignore-unresolvable="true" ignore-resource-not-found="false" location="classpath:property-file-name" />

0

यह सबसे अच्छा तरीका है जो मुझे काम करने के लिए मिला है:

package your.package;

import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;

public class ApplicationProperties {

    private Properties properties;

    public ApplicationProperties() {
        // application.properties located at src/main/resource
        Resource resource = new ClassPathResource("/application.properties");
        try {
            this.properties = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException ex) {
            Logger.getLogger(ApplicationProperties.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public String getProperty(String propertyName) {
        return this.properties.getProperty(propertyName);
    }
}

क्लास और कॉल विधि को तुरंत करें obj.getProperty ("my.property.name");
डैनियल अल्मेडा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.