Object-relational mapping frameworks like Hibernate and JPA offers developers the ability to use annotations to map relational database tables to Java objects. Spring LDAP project offers a similar ability with respect to LDAP directories through the use of the org.springframework.ldap.odm. This blog instruction we will discuss about OdmManager. The OdmManager orchestrates the process of reading objects from the directory and mapping the data to annotated Java object classes. This interface provides following methods:
<T> T findByDn(Name dn, Class<T> clazz) <T> T findOne(LdapQuery query, Class<T> clazz) <T> List<T> find(LdapQuery query, Class<T> clazz) <T> List<T> findAll(Class<T> clazz) <T> List<T> findAll(Name base, SearchControls searchControls, Class<T> clazz) <T> List<T> findAll(Name base, Filter filter, SearchControls searchControls, Class<T> clazz) void create(Object entry) void update(Object entry) void delete(Object entry)
-
Required Software
- Developemnt
- Testing
- Java test class used for testing.
- Create a Simple spring project name "spring-ldap-example". Following image will guide us. Add the required libraries in classpath. - spring-aop-4.1.3.RELEASE.jar - spring-beans-4.1.3.RELEASE.jar - spring-context-4.1.3.RELEASE.jar - spring-context-support-4.1.3.RELEASE.jar - spring-core-4.1.3.RELEASE.jar - spring-expression-4.1.3.RELEASE.jar - spring-ldap-core-2.0.2.RELEASE.jar - spring-ldap-core-tiger-2.0.2.RELEASE.jar - spring-ldap-ldif-batch-2.0.2.RELEASE.jar - spring-ldap-ldif-core-2.0.2.RELEASE.jar - spring-ldap-test-2.0.2.RELEASE.jar - log4j-1.2.14.jar - slf4j-jcl-1.7.5.jar
- Create folder name resource and add in classpath. Create test_data.ldif in resource folder and following snippet. This test_data.ldif is used to prepare test data.
- Create package ldap.odm.example
- Create simple pojo class User and following snippet.
- Create interface UserRepositoryIntf and add following snippet. We will define all following methods to implements example of SCRUD functions.
- Create class UserRepositoryImpl implements interface UserRepositoryIntf and add following snippet.
- Following table will explain the methods that used to SCRUD functionalities.
- Try to find and use a Converter registered for the fromClass, syntax and toClass and use it.
- If this fails, then if the toClass isAssignableFrom the fromClass then just assign it.
- If this fails try to find and use a Converter registered for the fromClass and the toClass ignoring the syntax.
- If this fails then throw a ConverterException.
- org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter used for String to Object conversion.
- org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter used for Object to String conversion.
- org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean used for providing converter configurations. We refer this bean while creating OdmManager bean.
- Create log4j.xml in resource folder and add following snippet.
version: 1
dn: dc=example,dc=com
objectclass: top
objectclass: domain
dc: example
dn: ou=groups,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
description: this will contains all the groups
dn: uid=kaustuv,ou=users,dc=example,dc=com
objectClass: organizationalPerson
objectClass: person
objectClass: uidObject
objectClass: top
cn: kaustuv
sn: maji
uid: kaustuv
postalAddress: GC 207, Sector III, SaltlakeCity, Kolkata 700106, WestBengal, India
telephoneNumber: 9831198311
userPassword:: e1NTSEF9OXg3VGxzamNrQkFWZmVRRllRYnBXS25IUFYvV0hpdmtiSFNNMXc9PQ==
dn: cn=testGroup,ou=groups,dc=example,dc=com
objectClass: top
objectClass: groupOfUniqueNames
cn: testGroup
uniqueMember: uid=kaustuv,ou=users,dc=example,dc=com
o: kaustuv's blog
dn: ou=users,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: users
dn: uid=guest,ou=users,dc=example,dc=com
objectClass: top
objectClass: uidObject
objectClass: person
objectClass: organizationalPerson
cn: guest
sn: guest
uid: guest
postalAddress: DreamLand
telephoneNumber: 9830098300
userPassword:: e1NTSEF9OXg3VGxzamNrQkFWZmVRRllRYnBXS25IUFYvV0hpdmtiSFNNMXc9PQ==
/**
*
* Copyright © Kaustuv Maji , 2014
* Repos - https://github.com/kaustuvmaji
* Blog - http://kaustuvmaji.blogspot.in
*
*/
package ldap.odm.example;
import java.io.Serializable;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
/**
* This class is mapped with ldap entry user
* @author KMaji
*
*/
@Entry(objectClasses = { "top", "uidObject", "person", "organizationalPerson" } , base="ou=users")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 9081527761576640803L;
@Id
private Name distinguisedName;
@Attribute(name = "uid")
@DnAttribute(value="uid", index=1)
private String uid;
@Attribute(name = "cn")
private String cn;
@Attribute(name = "sn")
private String sn;
@Attribute(name = "postalAddress")
private String postalAddress;
@Attribute(name = "telephoneNumber")
private String telephoneNumber;
/**
* @return the uid
*/
public synchronized final String getUid() {
return uid;
}
/**
* @param uid
* the uid to set
*/
public synchronized final void setUid(String uid) {
this.uid = uid;
}
/**
* @return the cn
*/
public synchronized final String getCn() {
return cn;
}
/**
* @param cn
* the cn to set
*/
public synchronized final void setCn(String cn) {
this.cn = cn;
}
/**
* @return the sn
*/
public synchronized final String getSn() {
return sn;
}
/**
* @param sn
* the sn to set
*/
public synchronized final void setSn(String sn) {
this.sn = sn;
}
/**
* @return the userPassword
*/
public synchronized final String getUserPassword() {
return userPassword;
}
/**
* @param userPassword
* the userPassword to set
*/
public synchronized final void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
/**
* @return the postalAddress
*/
public synchronized final String getPostalAddress() {
return postalAddress;
}
/**
* @param postalAddress
* the postalAddress to set
*/
public synchronized final void setPostalAddress(String postalAddress) {
this.postalAddress = postalAddress;
}
/**
* @return the telephoneNumber
*/
public synchronized final String getTelephoneNumber() {
return telephoneNumber;
}
/**
* @param telephoneNumber
* the telephoneNumber to set
*/
public synchronized final void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("User [");
if (uid != null) {
builder.append("uid=");
builder.append(uid);
builder.append(", ");
}
if (cn != null) {
builder.append("cn=");
builder.append(cn);
builder.append(", ");
}
if (sn != null) {
builder.append("sn=");
builder.append(sn);
builder.append(", ");
}
if (userPassword != null) {
builder.append("userPassword=");
builder.append(userPassword);
builder.append(", ");
}
if (postalAddress != null) {
builder.append("postalAddress=");
builder.append(postalAddress);
builder.append(", ");
}
if (telephoneNumber != null) {
builder.append("telephoneNumber=");
builder.append(telephoneNumber);
}
builder.append("]");
return builder.toString();
}
}
following table explains possible annotation options provided by spring ldap odm framework.
| Annotation Type | Description |
|---|---|
| Entry | This annotation marks a Java class to be persisted in an LDAP directory. It is recommended to use this annotation at class level. Example: @Entry(objectClasses = { "top", "uidObject", "person", "organizationalPerson" }) |
| Id | This annotation marks a Java field as containing the Distinguished Name of an LDAP Entry. Example : @Id private Name distinguisedName; |
| Attribute | This annotation describes the mapping of a Java field to an LDAP attribute. Example: @Attribute(name = "uid") private String uid; |
| DnAttribute | Indicates that a field is to be automatically populated to/from the distinguished name of an entry. Example: @Attribute(name = "uid") @DnAttribute(value="uid", index=1) private String uid; |
| Transient | This annotation identifies a field in an Entry annotated class that should not be persisted to LDAP. |
/**
*
* Copyright © Kaustuv Maji , 2014
* Repos - https://github.com/kaustuvmaji
* Blog - http://kaustuvmaji.blogspot.in
*
*/
package ldap.advance.example;
import java.util.List;
/**
* <pre>
* This interface is used for
* a) fetch all the user details as a list of String
* b) fetch all the user details as a list of User object
* c) fetch user details of particular user.
* </pre>
*
* @author KMaji
*
*/
public interface UserRepositoryIntf <T>{
/**
* This method is responsible to fetch all the user details as a list of
* String.
*
* @return list of String.
*/
public List<String> getAllUserNames();
/**
* This method is responsible to fetch all the user details as a list of
* User object
*
* @return list of {@link T}
*/
public List<T> getAllUsers();
/**
* This method is responsible to fetch user details of particular user.
*
* @return user details {@link T}
*/
public T getUserDetails(String userName);
/**
* This method is responsible to fetch user details of particular user as a string.
*
* @return user detail {@link T}
*/
public Object getUserDetail(String userName);
/**
* This method is responsible to authenticate user.
*
* @return boolean true|false
*/
public boolean authenticate(String base,String userName, String password);
/**
* This method is responsible to update telephone number of user.
*
* @return boolean true|false
*/
public T updateTelePhone(String userName, String newNumber);
/**
* This method is responsible to create user.
*/
public boolean createUser(T user);
/**
* This method is responsible to delete user.
*/
public boolean remove(String uid);
}
/**
*
* Copyright © Kaustuv Maji , 2014
* Repos - https://github.com/kaustuvmaji
* Blog - http://kaustuvmaji.blogspot.in
*
*/
package ldap.odm.example;
import java.util.List;
import javax.naming.directory.SearchControls;
import ldap.advance.example.UserRepositoryIntf;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.stereotype.Component;
/**
* This class implements the @see {@link UserRepositoryIntf}.
*
* @author KMaji
*
*/
@Component
public class UserRepositoryImpl implements UserRepositoryIntf<User> {
private static Logger log = Logger.getLogger(UserRepositoryImpl.class);
private static final String basedn = "ou=users";
private static final String queryDelimeter = ",";
@SuppressWarnings("deprecation")
@Autowired
private OdmManager odmManager;
@Autowired(required = true)
@Qualifier(value = "ldapTemplate")
private LdapTemplate ldapTemplate;
/**
* (non-Javadoc)
*
* @see ldap.advance.example.UserRepositoryIntf#getAllUserNames()
**/
@Override
public List<String> getAllUserNames() {
return null;
}
/**
* (non-Javadoc)
*
* @see ldap.advance.example.UserRepositoryIntf#getAllUsers()
**/
@SuppressWarnings("deprecation")
@Override
public List<User> getAllUsers() {
SearchControls controls = new SearchControls();
// controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
return odmManager.search(User.class, new DistinguishedName(basedn), "(uid=*)", controls);
}
/**
* (non-Javadoc)
*
* @see
* ldap.advance.example.UserRepositoryIntf#getUserDetails(java.lang.String)
**/
@Override
public User getUserDetails(String userName) {
return null;
}
/**
* (non-Javadoc)
*
* @see
* ldap.advance.example.UserRepositoryIntf#getUserDetail(java.lang.String)
**/
@SuppressWarnings("deprecation")
@Override
public User getUserDetail(String userName) {
log.info("executing {getUserDetail}");
log.info(bindDN(userName));
User rUser = odmManager.read(User.class, bindDN(userName));
return rUser;
}
/**
* (non-Javadoc)
*
* @see
* ldap.advance.example.UserRepositoryIntf#authenticate(java.lang.String,
* java.lang.String)
**/
@Override
public boolean authenticate(String base, String userName, String password) {
log.info("executing {authenticate}");
return false;
}
/**
* (non-Javadoc)
*
* @see
* ldap.advance.example.UserRepositoryIntf#updateTelePhone(java.lang.String)
**/
@SuppressWarnings("deprecation")
@Override
public User updateTelePhone(String userName, String newNumber) {
log.info("executing {updateTelePhone}");
User mUser = odmManager.read(User.class, bindDN(userName));
mUser.setTelephoneNumber(newNumber);
odmManager.update(mUser);
return getUserDetail(userName);
}
/**
* (non-Javadoc)
*
* @see
* ldap.advance.example.UserRepositoryIntf#createUser(ldap.advance.example
* .User)
**/
@SuppressWarnings("deprecation")
@Override
public boolean createUser(User user) {
log.info("executing {createUser}");
user.setDistinguisedName(bindDN(user.getUid()));
odmManager.create(user);
return true;
}
/**
* (non-Javadoc)
*
* @see ldap.advance.example.UserRepositoryIntf#remove(java.lang.String)
**/
@SuppressWarnings("deprecation")
@Override
public boolean remove(String uid) {
// ldapTemplate.unbind(bindDN(uid));
odmManager.delete(getUserDetail(uid));
return true;
}
@SuppressWarnings("deprecation")
public static javax.naming.Name bindDN(String userName) {
javax.naming.Name name = new DistinguishedName("uid=".concat(userName).concat(queryDelimeter.concat(basedn)));
return name;
}
}
| Methods name | Description |
|---|---|
| createUser | This method is responsible to create user. - Example of ldapTemplate.bind() and BasicAttribute. |
| updateTelePhone | This method is responsible to update telephone number of user. - Example of ldapTemplate.modifyAttributes and ModificationItem. |
| remove | This method is responsible to delete user. - Example of ldapTemplate.unbind(). |
| getAllUsers | This method is responsible to fetch all the user details as a list of User object. - ldapTemplate.search() is used to search data. - Example of SearchControls. |
| getUserDetail | This method is responsible to fetch user details of particular user as a string. |
<?xml version="1.0" encoding="UTF-8"?>
<!--
#
# Copyright © Kaustuv Maji , 2014
# Repos - https://github.com/kaustuvmaji
# Blog - http://kaustuvmaji.blogspot.in
#
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:ldap="http://www.springframework.org/schema/ldap"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/ldap http://www.springframework.org/schema/ldap/spring-ldap-2.0.xsd">
<context:component-scan base-package="ldap.odm.example" />
<bean id="simpleDirContextAuthenticationStrategy"
class="org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy" />
<bean id="userReposImpl" class="ldap.odm.example.UserRepositoryImpl" />
<bean id="fromStringConverter"
class="org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter" />
<bean id="toStringConverter"
class="org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter" />
<!-- The OdmManager relies on the org.springframework.ldap.odm.typeconversion package to convert LDAP attributes
to Java fields. The main interface in this class is the org.springframework.ldap.odm.typeconversion.ConverterManager. --"
<bean id="converterManager"
class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
<property name="converterConfig">
<set>
<bean
class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
<property name="fromClasses">
<set>
<value>java.lang.String</value>
</set>
</property>
<property name="toClasses">
<set>
<value>java.lang.Byte</value>
<value>java.lang.Short</value>
<value>java.lang.Integer</value>
<value>java.lang.Long</value>
<value>java.lang.Float</value>
<value>java.lang.Double</value>
<value>java.lang.Boolean</value>
</set>
</property>
<property name="converter" ref="fromStringConverter" />
</bean>
<bean
class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
<property name="fromClasses">
<set>
<value>java.lang.Byte</value>
<value>java.lang.Short</value>
<value>java.lang.Integer</value>
<value>java.lang.Long</value>
<value>java.lang.Float</value>
<value>java.lang.Double</value>
<value>java.lang.Boolean</value>
</set>
</property>
<property name="toClasses">
<set>
<value>java.lang.String</value>
</set>
</property>
<property name="converter" ref="toStringConverter" />
</bean>
</set>
</property>
</bean>
<ldap:context-source url="ldap://localhost:10389"
base="dc=example,dc=com" username="uid=admin,ou=system" password="secret"
authentication-strategy-ref="simpleDirContextAuthenticationStrategy"
native-pooling="true" />
<bean id="odmManager"
class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">
<property name="converterManager" ref="converterManager" />
<property name="contextSource" ref="contextSource" />
<property name="managedClasses">
<set>
<value>ldap.odm.example.User</value>
</set>
</property>
</bean>
<ldap:ldap-template id="ldapTemplate" />
</beans>
The OdmManager depends on the typeconversion provided by spring ldap odm framework to convert LDAP attributes to Java fields. The main interface in this class is the ConverterManager. The default ConverterManager implementation uses the following algorithm when parsing objects to convert fields:
The factory bean requires converter configurations to be declared in the bean configuration. In our example we used default typeconversion configuration recommended by spring framework. we used following steps.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">
<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ISO8601} %-5p %-1X{TID} %t [%c] %m%n" />
</layout>
</appender>
<!-- Appenders -->
<appender name="file" class="org.apache.log4j.RollingFileAppender">
<param name="Threshold" value="ALL" />
<param name="File" value="diag.log" />
<param name="Append" value="true" />
<param name="MaxFileSize" value="500000KB" />
<param name="MaxBackupIndex" value="10" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ISO8601} %-5p %-1X{TID} %t [%c] %m%n" />
</layout>
</appender>
<!-- Root Logger -->
<root>
<priority value="INFO" />
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
</log4j:configuration>
Post Development testing
Following class will be used to test spring ldap template examples.
/**
*
* Copyright © Kaustuv Maji , 2015
* Repos - https://github.com/kaustuvmaji
* Blog - http://kaustuvmaji.blogspot.in
*
*/
package ldap.odm.example.test;
import java.util.Date;
import ldap.odm.example.User;
import ldap.advance.example.UserRepositoryIntf;
import org.apache.log4j.Logger;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author KMaji
*
*/
public class LdapODMApp {
private static Logger log = Logger.getLogger(LdapODMApp.class);
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-ldap-odm.xml");
log.info("Test started at " + new Date(context.getStartupDate()));
@SuppressWarnings("unchecked")
UserRepositoryIntf<User> ldapDao = (UserRepositoryIntf<User>) context.getBean("userReposImpl");
final String success = "Success";
final String failure = "Failure";
final String username = "kaustuv";
{
// Create
User user = new User();
{
user.setUid("spring_ldap_test");
user.setCn("spring_ldap_test");
user.setSn("spring_ldap_test");
user.setPostalAddress("spring_ldap_test");
user.setTelephoneNumber("9830098301");
}
log.info("\n Example of OdmManager create #=> " + (ldapDao.createUser(user) ? success : failure));
// Read
log.info("\n Example of OdmManager search all #=> " + ldapDao.getAllUsers());
// find
log.info("\n Example of OdmManager read #=> " + ldapDao.getUserDetail(username));
// Update
log.info("\n Example of OdmManager update #=> " + ldapDao.updateTelePhone("spring_ldap_test", "9831198311"));
// Delete
log.info("\n Example of OdmManager delete #=> " + (ldapDao.remove("spring_ldap_test") ? success : failure));
}
context.registerShutdownHook();
context.close();
}
}
Output
2015-05-09 10:33:19,784 INFO main [org.springframework.context.support.ClassPathXmlApplicationContext] Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c2e1f26: startup date [Sat May 09 10:33:19 IST 2015]; root of context hierarchy
2015-05-09 10:33:19,949 INFO main [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [spring-ldap-odm.xml]
2015-05-09 10:33:20,925 INFO main [ldap.odm.example.test.LdapODMApp] Test started at Sat May 09 10:33:19 IST 2015
2015-05-09 10:33:20,926 INFO main [ldap.odm.example.UserRepositoryImpl] executing {createUser}
2015-05-09 10:33:22,051 INFO main [ldap.odm.example.test.LdapODMApp]
Example of OdmManager create #=> Success
2015-05-09 10:33:22,086 INFO main [ldap.odm.example.test.LdapODMApp]
Example of OdmManager search all #=> [User [uid=spring_ldap_test, cn=spring_ldap_test, sn=spring_ldap_test, postalAddress=spring_ldap_test, telephoneNumber=9830098301], User [uid=kaustuv, cn=kaustuv, sn=maji, postalAddress=GC 207, Sector III, SaltlakeCity, Kolkata 700106, WestBengal, India, telephoneNumber=9831198311], User [uid=guest, cn=guest, sn=guest, postalAddress=DreamLand, telephoneNumber=9830098300]]
2015-05-09 10:33:22,086 INFO main [ldap.odm.example.UserRepositoryImpl] executing {getUserDetail}
2015-05-09 10:33:22,087 INFO main [ldap.odm.example.UserRepositoryImpl] uid=kaustuv,ou=users
2015-05-09 10:33:22,097 INFO main [ldap.odm.example.test.LdapODMApp]
Example of OdmManager read #=> User [uid=kaustuv, cn=kaustuv, sn=maji, postalAddress=GC 207, Sector III, SaltlakeCity, Kolkata 700106, WestBengal, India, telephoneNumber=9831198311]
2015-05-09 10:33:22,102 INFO main [ldap.odm.example.UserRepositoryImpl] executing {updateTelePhone}
2015-05-09 10:33:22,941 INFO main [ldap.odm.example.UserRepositoryImpl] executing {getUserDetail}
2015-05-09 10:33:22,942 INFO main [ldap.odm.example.UserRepositoryImpl] uid=spring_ldap_test,ou=users
2015-05-09 10:33:22,948 INFO main [ldap.odm.example.test.LdapODMApp]
Example of OdmManager update #=> User [uid=spring_ldap_test, cn=spring_ldap_test, sn=spring_ldap_test, postalAddress=spring_ldap_test, telephoneNumber=9831198311]
2015-05-09 10:33:22,948 INFO main [ldap.odm.example.UserRepositoryImpl] executing {getUserDetail}
2015-05-09 10:33:22,948 INFO main [ldap.odm.example.UserRepositoryImpl] uid=spring_ldap_test,ou=users
2015-05-09 10:33:23,854 INFO main [ldap.odm.example.test.LdapODMApp]
Example of OdmManager delete #=> Success
Source Code
- References:
European Union laws require you to give European Union visitors information about cookies used on your blog. In many cases, these laws also require you to obtain consent.
As a courtesy, we have added a notice on your blog to explain Google's use of certain Blogger and Google cookies, including use of Google Analytics and AdSense cookies.
No comments:
Post a Comment